n2n-nexus 0.4.2 → 0.7.0
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/CHANGELOG.md +114 -0
- package/README.md +77 -117
- package/assets/n2n-nexus-banner.jpg +0 -0
- package/assets/n2n-nexus-icon.png +0 -0
- package/assets/n2n-nexus-logo.png +0 -0
- package/build/client/nexus-client.js +44 -21
- package/build/config/cli.js +0 -3
- package/build/config/index.js +0 -10
- package/build/constants.js +7 -11
- package/build/daemon/index.js +35 -19
- package/build/daemon/rate-limit.js +62 -0
- package/build/daemon/server.js +422 -545
- package/build/daemon/tool-outputs.js +177 -0
- package/build/daemon/tools.js +321 -0
- package/build/index.js +1 -1
- package/build/server/instance-id.js +19 -0
- package/build/server/nexus.js +243 -47
- package/build/storage/direct-messages.js +89 -0
- package/build/storage/docs.js +14 -7
- package/build/storage/events.js +31 -0
- package/build/storage/fs-utils.js +42 -0
- package/build/storage/global-messages.js +44 -0
- package/build/storage/index.js +47 -41
- package/build/storage/migrations.js +86 -0
- package/build/storage/paths.js +0 -3
- package/build/storage/projects.js +4 -5
- package/build/storage/registry.js +67 -25
- package/build/storage/sessions.js +77 -0
- package/build/storage/sqlite-meeting.js +35 -23
- package/build/storage/sqlite.js +73 -24
- package/build/storage/tasks.js +29 -103
- package/build/types.js +1 -1
- package/docs/ARCHITECTURE.md +36 -25
- package/docs/ARCHITECTURE_zh.md +37 -26
- package/docs/ASSISTANT_GUIDE.md +77 -65
- package/docs/README_zh.md +34 -19
- package/llms.txt +12 -2
- package/package.json +24 -14
- package/build/storage/logs.js +0 -60
- package/build/storage/meetings.js +0 -276
- package/build/storage/store.js +0 -153
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.7.0] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Session discovery with `list_sessions` and `update_session`: each MCP connection has a distinct session ID, a display name, optional project association, an incoming-message policy and heartbeat-based presence. Connections sharing an instance ID have separate inboxes.
|
|
10
|
+
- Direct messages through `send_message(to, replyTo?, ttlMs?)`, plus `read_inbox`, `get_message` and `acknowledge_message`. Receipts distinguish accepted, fetched, acknowledged, replied, refused and expired messages. Unacknowledged fetched messages remain retrievable, and replies link back to the original message.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- MCP adapters register their connection identity and maintain it with heartbeats. Stable instance IDs continue to identify legacy shared-message cursors and meeting permissions.
|
|
15
|
+
- Updated compatible runtime and development dependencies, including Zod, ESLint, typescript-eslint, Vitest and Lefthook.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Strategy migration now records completion and preserves conflicting documents, preventing later user documents from being mistaken for legacy data and deleted on restart.
|
|
20
|
+
- Interrupted HTTP responses now reject the pending request instead of leaving connection attempts, tool calls or shutdown waiting indefinitely.
|
|
21
|
+
- Malformed Host headers and request URLs return HTTP 400 without terminating the daemon; existing browser and Host restrictions remain in effect.
|
|
22
|
+
- Concurrent attempts to rename one project to different destinations no longer both report success or record a rename that did not happen.
|
|
23
|
+
- MCP adapters can connect to IPv6 daemon endpoints without treating the address brackets as part of a DNS hostname.
|
|
24
|
+
- Task expiry preserves timestamp milliseconds so maintenance does not fail tasks before their TTL has elapsed.
|
|
25
|
+
|
|
26
|
+
### Upgrade notes
|
|
27
|
+
|
|
28
|
+
- Upgrade the daemon first, then restart the MCP adapters on 0.7.0. New adapters require the daemon's session registration endpoints before executing tools. Existing meeting and global-message operations remain available with the upgraded daemon.
|
|
29
|
+
- Inboxes are per MCP connection. Host chats sharing one connection share its inbox, and restarting the adapter creates a new session rather than inheriting the previous inbox.
|
|
30
|
+
- Incoming messages require an explicit `read_inbox` call; this release does not automatically wake models. Online presence indicates a recent heartbeat, fetching does not prove processing, and acknowledgement confirms receipt rather than completion of work.
|
|
31
|
+
|
|
32
|
+
## [0.6.0] - 2026-10-16
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Rate limiting, which the MCP tools specification requires of servers: a token bucket per instance ID (default 20 calls/s, burst 60; `NEXUS_RATE_LIMIT`, `NEXUS_RATE_BURST`, `0` disables) plus one per client address at five times that, since instance IDs are self-declared. Requests without an instance ID (most REST reads) are limited per address only. Over the limit the daemon answers `429` with `Retry-After`; the MCP adapter reports it to the model as a tool error and keeps the connection. `/health` and `GET /api/tools` are exempt.
|
|
37
|
+
|
|
38
|
+
- Structured tool results: every successful call returns `structuredContent` alongside the JSON text, and the eleven data-returning tools publish an `outputSchema`. Result shapes are zod contracts in `src/daemon/tool-outputs.ts` that type the handlers; a test calls all tools through the adapter for both protocol eras and checks each result against its contract. Publishing schemas for write tools as well would have grown `tools/list` from about 14 KB to 27 KB for a one-line acknowledgement, so they are left out (22 KB with the read schemas).
|
|
39
|
+
|
|
40
|
+
- A display `title` on every tool (for example "Delete Project"), which MCP clients show instead of the raw tool name.
|
|
41
|
+
|
|
42
|
+
- MCP tool annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) on every tool, so clients can tell read-only tools from ones that overwrite or delete data and ask the user before the latter.
|
|
43
|
+
|
|
44
|
+
- `list_meetings` tool and `GET /api/meetings`: list meetings with status, initiator, participants, message and decision counts and summary. Assistants could only reach a meeting whose id they already knew.
|
|
45
|
+
|
|
46
|
+
- `get_project` tool and `GET /api/projects/:projectId`: read a project's full manifest and internal docs. Assistants could sync them but had no tool to read them back.
|
|
47
|
+
|
|
48
|
+
- `get_global_strategy`, `list_global_docs` and `get_global_doc` tools. Assistants could write the strategy and shared docs but had no tool to read them back; only the REST API could.
|
|
49
|
+
|
|
50
|
+
- `list_events` tool and `GET /api/events`: a structured activity log (actor, action, target, detail) of every state change made through the daemon.
|
|
51
|
+
|
|
52
|
+
### Removed
|
|
53
|
+
|
|
54
|
+
- `register_session_context` and `POST /api/session/register`. The daemon kept an in-memory "current project" per instance that only `upload_project_asset` used; MCP 2026-07-28 makes protocol sessions go away and recommends passing cross-call state as explicit tool arguments. `upload_project_asset` now takes `projectId` and checks that the project exists.
|
|
55
|
+
|
|
56
|
+
- Leftovers from the pre-0.5 election architecture and other dead code: unused constants (`PORT_RANGE_*`, `HANDSHAKE_TIMEOUT`, `HEARTBEAT_INTERVAL`), `hasFlag`, `HubConfig`/`ToolContext`/`MeetingState` types, `isSqliteAvailable`, `getTasksByMeeting`, `getActiveTaskCount`, `NexusClient.health`, the never-used `archives/` directory and `tasks.json` path, `suggestedSyncTargets` (parsed a `name@project` instance-id format that no longer exists), the undocumented `NEXUS_PORT` alias of `NEXUS_DAEMON_PORT`, and `migrations/003_add_tasks_table.sql` (the schema lives in `sqlite.ts`). The two `getRootPath` implementations are merged into `config/paths.ts`, so `--root ./relative` and `~` are resolved consistently. `billing` is now preserved on read and patch instead of being dropped.
|
|
57
|
+
|
|
58
|
+
- JSON meeting-store fallback (`store.ts`, `meetings.ts`) and the `/api/storage/info` endpoint. SQLite is required; the daemon no longer reports a "degraded" storage mode.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- `NEXUS_RATE_BURST` between 0 and 1 made every call fail with 429 forever, and `NEXUS_RATE_BURST=0` silently disabled rate limiting. The burst is now at least 1, and only `NEXUS_RATE_LIMIT=0` disables limiting.
|
|
63
|
+
|
|
64
|
+
- Manifests written by earlier releases (whose `update_project` stored patches unvalidated) could hold nested relations, endpoints or API specs with missing or extra fields, which made `get_project` and `get_global_topology` fail outputSchema validation on 2026-07-28 clients. Reads now normalize stored manifests: invalid entries are dropped, missing strings become empty, an unknown relation type reads as `related`. The files are not rewritten.
|
|
65
|
+
|
|
66
|
+
- Result contracts were only enforced by TypeScript, which ignores extra fields on returned variables, so a new field in stored data could reach clients as structuredContent that violates the published `outputSchema`. Every result now passes through its zod contract on the way out: undeclared fields are stripped, and a result that cannot meet the contract becomes a server error.
|
|
67
|
+
|
|
68
|
+
- If a tool call lost the daemon while a reconnect attempt that had just succeeded was still notifying the client, the attempt then stopped the retry loop although the adapter was disconnected again; every later call answered "Daemon is not ready yet" until the IDE restarted. The loop now stops only if the connection is still up.
|
|
69
|
+
|
|
70
|
+
- The adapter's reconnect loop used a 3-second `setInterval` while each attempt could take up to the 5-second request timeout, so a hung daemon accumulated overlapping requests. Each attempt is now scheduled only after the previous one finishes.
|
|
71
|
+
|
|
72
|
+
- `rename_project`, `cancel_task`, `end_meeting`, `archive_meeting` and `reopen_meeting` were annotated `destructiveHint: false`, which MCP reserves for purely additive tools; clients could run them without asking the user. They are now marked destructive.
|
|
73
|
+
|
|
74
|
+
- When the daemon reported a tool as unknown (for example after an upgrade the adapter did not notice), the adapter returned `-32602` but kept offering the tool in `tools/list`. It now re-fetches the tool list and sends `list_changed` if it differs.
|
|
75
|
+
|
|
76
|
+
- The adapter fetched the daemon tool list before it began reading stdin. A daemon that accepted connections but never answered held `server/discover` and `initialize` for the 5-second client timeout, long enough for probing clients to misclassify the server as legacy, and delayed exit after stdin closed. The adapter now serves immediately and fetches in the background.
|
|
77
|
+
|
|
78
|
+
- On 2026-07-28 connections, a request without `io.modelcontextprotocol/clientInfo` (only a SHOULD in the spec) got an instance ID without the IDE name, so one client could appear as two instances with split unread cursors and meeting permissions. The adapter now keeps the first client name seen on the connection.
|
|
79
|
+
|
|
80
|
+
- Calling a tool the adapter does not know returned a tool result with `isError: true`. The MCP specification classifies an unknown tool as a protocol error; the adapter now answers with JSON-RPC error `-32602` (Invalid Params), also when the daemon reports the tool as unknown.
|
|
81
|
+
|
|
82
|
+
- The MCP adapter did not exit when the IDE closed its stdin: while the daemon was unreachable the 3-second reconnect timer kept the process alive indefinitely, leaving orphaned adapters. The adapter now shuts down and exits when stdin ends, as the stdio transport specification requires.
|
|
83
|
+
|
|
84
|
+
- Project and document ids that match `Object.prototype` keys were mishandled: `register_session_context({ projectId: "constructor" })` reported success for a project that did not exist, and a project or doc named `__proto__` was silently lost because assigning it rewrote the map's prototype. The registry and docs index are now prototype-free maps.
|
|
85
|
+
|
|
86
|
+
- `sync_project_assets` wrote the manifest and the internal docs as two separately locked steps, so a concurrent `host_delete_project` could run between them and leave a project directory on disk that the registry no longer knew about. Both writes now happen in one critical section.
|
|
87
|
+
|
|
88
|
+
- `npm run build` now clears `build/` first. `tsc` alone left compiled files of deleted sources behind (for example `build/storage/store.js`), and `npm publish` would have shipped them.
|
|
89
|
+
|
|
90
|
+
- `ttl` on tasks was stored but never enforced and `cleanupTasks` was never scheduled, so the tasks table grew forever. The daemon now runs task maintenance at startup and hourly: pending/running tasks past their TTL are marked `failed` (`TTL expired`) and finished tasks older than 7 days are purged. The `tasks_updated_at` trigger is dropped: it overwrote the ISO 8601 `updated_at` written by `updateTask` with `datetime('now')` in a different format, so age comparisons mixed two timestamp formats.
|
|
91
|
+
|
|
92
|
+
- `update_global_strategy` wrote `global/docs/strategy.md` while the documentation described `global/blueprint.md` as the master strategy document, and two initializers created `blueprint.md` with different placeholder text. The tool now writes `blueprint.md`; `GET /api/global/strategy` reads it. On first start an existing `strategy` global doc is moved into `blueprint.md` (unless a non-placeholder blueprint is already there) and removed from the docs index.
|
|
93
|
+
|
|
94
|
+
- `sqlite.ts` registered SIGINT/SIGTERM handlers at import time that called `process.exit(0)` immediately, so the daemon's graceful `server.close` never ran. Signal handling now lives in the daemon entry point: stop accepting connections, finish in-flight requests, close the database, exit.
|
|
95
|
+
|
|
96
|
+
- Tool arguments were never validated: the daemon cast whatever arrived and let SQLite or the filesystem fail later (for example `category: "SYSTEM"` surfaced as a `CHECK constraint failed` 500). Every tool now has a zod contract in `src/daemon/tools.ts`; the JSON Schema served to MCP clients is generated from it and the same contract validates tool calls and REST endpoints, returning 400 with the failing field. `patch` for `update_project` rejects unknown fields, `create_task` checks that `source_meeting_id` exists, and SQLite foreign keys are enforced.
|
|
97
|
+
|
|
98
|
+
- The MCP adapter generated a random instance ID on every start, so an IDE restart reset unread cursors and left meetings that no one could end or archive (only the initiator may). The default is now `mcp-<hostname>-<hash of working directory>-<IDE name>` (IDE name from the MCP handshake), stable per machine, workspace and IDE. `reopen_meeting` applies the same initiator check as `end_meeting` and `archive_meeting`.
|
|
99
|
+
|
|
100
|
+
- The daemon accepted any HTTP request, so a web page open in the user's browser could call `host_delete_project` on the local daemon with a cross-site `text/plain` POST. Requests carrying a browser `Origin` or cross-site `Sec-Fetch-Site` header are refused (403), POST requires `Content-Type: application/json` (415), and request bodies are capped at 16 MB (413) instead of being buffered without limit. The `Host` header must be an IP literal, `localhost`, `host.docker.internal` or a name in `NEXUS_ALLOWED_HOSTS`, which blocks DNS-rebinding pages that would otherwise reach the daemon as same-origin GETs.
|
|
101
|
+
|
|
102
|
+
- The MCP adapter treated every failed tool call (unknown task, closed meeting, permission denied, ...) as the daemon going away: it cleared the tool list, notified the IDE and entered the reconnect loop. Transport failures and tool failures are now distinct (`NexusTransportError` vs `NexusToolError`); only the former triggers reconnection, and tool failures are returned as `isError` results. `POST /api/tools/call` answers 404 for an unknown tool and 400 when the tool rejects the call.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- The MCP adapter moved from `@modelcontextprotocol/sdk` 1.x (protocol 2025-11-25) to the v2 SDK (`@modelcontextprotocol/server` 2.3) and serves both the stateless 2026-07-28 revision and 2025-era clients over stdio via `serveStdio`. 2026-07-28 clients get `server/discover`, the per-request `_meta` envelope and list-changed notifications on `subscriptions/listen`; the instance ID reads the client name from the request envelope. The first `tools/list` waits up to one second for the startup daemon fetch, so it is complete whenever a local daemon is up, while `server/discover` and `initialize` are answered immediately even if the daemon hangs.
|
|
107
|
+
|
|
108
|
+
- Tools without parameters (`get_global_strategy`, `list_global_docs`) publish `{ "type": "object", "properties": {}, "additionalProperties": false }`, the form the MCP specification recommends, and reject unexpected arguments.
|
|
109
|
+
|
|
110
|
+
- `send_message` routing no longer depends on `register_session_context`: a message goes to `meetingId` when given, else to the active meeting, else to global chat. `POST /api/messages/send` uses the same rule (its `source` parameter is gone) instead of a separate code path with different defaults.
|
|
111
|
+
|
|
112
|
+
- Global chat moved from `global/discussion.json` into SQLite. `read_messages` without `meetingId` is now truly incremental per instance (the tool description had promised this), appends no longer rewrite the whole file, and `host_maintenance` reports how many messages were removed. On first start the daemon imports an existing `discussion.json` (categories outside the five known values become unset) and renames it to `discussion.json.migrated`.
|
|
113
|
+
- Daemon state changes (session register, project sync/rename/delete, meeting end, ...) are recorded as events instead of `SYSTEM` lines in the global chat.
|
|
114
|
+
|
|
115
|
+
- `sync_project_assets`, `update_project`, `rename_project`, and `host_delete_project` now run synchronously and return their result directly instead of a `task_id` to poll. The operations take milliseconds; the background-task wrapper only added a polling round-trip and let the work race with other requests.
|
|
116
|
+
|
|
117
|
+
- Require Node.js 22.14+ on the 22.x release line, or Node.js 23.6+ and update MCP SDK, SQLite, and test dependencies to their latest compatible stable releases.
|
|
118
|
+
|
|
5
119
|
## [0.5.0] - 2026-06-14
|
|
6
120
|
|
|
7
121
|
### 🏗️ Daemon + MCP Architecture (Breaking Refactor)
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/n2n-nexus-logo.png" width="128" alt="n2n-nexus logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# n2n-nexus
|
|
2
6
|
|
|
3
7
|
Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaboration across IDEs, machines, and projects.
|
|
@@ -7,7 +11,6 @@ Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaborat
|
|
|
7
11
|
[](https://github.com/n2ns/n2n-nexus/blob/main/LICENSE)
|
|
8
12
|
[](https://modelcontextprotocol.io)
|
|
9
13
|
[](https://nodejs.org)
|
|
10
|
-
[](https://datafrog.io)
|
|
11
14
|
|
|
12
15
|
[中文版](./docs/README_zh.md)
|
|
13
16
|
|
|
@@ -15,45 +18,33 @@ Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaborat
|
|
|
15
18
|
|
|
16
19
|
> **One daemon. Many assistants. Shared project state.**
|
|
17
20
|
|
|
18
|
-
n2n-nexus is an open-source Model Context Protocol (MCP) coordination server for
|
|
19
|
-
|
|
20
|
-
Use it when Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, JetBrains, or other MCP-enabled clients need a shared local coordination layer instead of isolated per-chat context.
|
|
21
|
-
|
|
22
|
-
## Search-friendly positioning
|
|
23
|
-
|
|
24
|
-
If you are searching for:
|
|
25
|
-
|
|
26
|
-
- multi-agent MCP coordination
|
|
27
|
-
- multi-AI assistant collaboration
|
|
28
|
-
- local MCP coordination hub
|
|
29
|
-
- shared project context for AI coding agents
|
|
30
|
-
- cross-IDE MCP collaboration server
|
|
31
|
-
- local-first project registry for AI assistants
|
|
32
|
-
- meeting and task coordination for coding agents
|
|
21
|
+
n2n-nexus is an open-source Model Context Protocol (MCP) coordination server for developers and teams who use multiple AI coding assistants across IDEs, machines, and projects. Use it when Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, JetBrains, or other MCP-enabled clients need a shared local coordination layer instead of isolated per-chat context.
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
## 📚 Contents
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
- [What is n2n-nexus?](#-what-is-n2n-nexus)
|
|
26
|
+
- [Architecture](#️-architecture)
|
|
27
|
+
- [Quick start](#-quick-start)
|
|
28
|
+
- [Toolset](#️-toolset)
|
|
29
|
+
- [Data storage](#-data-storage)
|
|
30
|
+
- [Project ID conventions](#️-project-id-conventions)
|
|
31
|
+
- [CLI reference](#-cli-reference)
|
|
32
|
+
- [Security and governance notes](#-security-and-governance-notes)
|
|
33
|
+
- [Real-world example](#-real-world-example)
|
|
34
|
+
- [Local development](#-local-development)
|
|
35
|
+
- [Related docs](#-related-docs)
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
## 💡 What is n2n-nexus?
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- **Install**: `npx n2n-nexus daemon --port 5688`
|
|
43
|
-
- **Protocol**: Model Context Protocol (MCP), adapter-to-daemon HTTP bridge
|
|
44
|
-
- **Storage**: local filesystem plus SQLite under `~/.n2n-nexus` by default
|
|
45
|
-
- **Best for**: multi-assistant coding sessions, project handoffs, shared decisions, meeting notes, async tasks, project manifests
|
|
46
|
-
- **Not for**: cloud team chat, public project management SaaS, source code indexing, vector search, or remote database hosting
|
|
47
|
-
|
|
48
|
-
## Why Use It?
|
|
39
|
+
n2n-nexus gives AI assistants a shared coordination workspace. A long-running local daemon owns project state, meetings, messages, tasks, shared docs, and project assets. Lightweight MCP adapters connect each IDE or assistant to the same daemon.
|
|
49
40
|
|
|
50
41
|
- Coordinate multiple AI assistants working on the same project.
|
|
51
|
-
- Share project manifests,
|
|
52
|
-
- Keep decisions, proposals, and updates in a local meeting
|
|
53
|
-
- Run the daemon once
|
|
54
|
-
- Avoid giving each assistant broad filesystem or backend access
|
|
42
|
+
- Share project manifests, decisions, and assets across IDEs and machines.
|
|
43
|
+
- Keep decisions, proposals, and updates in a local meeting and message log.
|
|
44
|
+
- Run the daemon once; connect adapters from Windows, WSL, SSH hosts, or multiple editors.
|
|
45
|
+
- Avoid giving each assistant broad filesystem or backend access.
|
|
55
46
|
|
|
56
|
-
## Architecture
|
|
47
|
+
## 🏗️ Architecture
|
|
57
48
|
|
|
58
49
|
```text
|
|
59
50
|
┌──────────────────────────────────────┐
|
|
@@ -70,10 +61,12 @@ n2n-nexus gives AI assistants a shared coordination workspace. Instead of each a
|
|
|
70
61
|
```
|
|
71
62
|
|
|
72
63
|
- **Daemon is the source of truth**: start it once and keep it running.
|
|
73
|
-
- **MCP adapters
|
|
64
|
+
- **MCP adapters hold connection identity only**: each IDE starts an adapter through `npx`, then forwards tool calls to the daemon, which stores the session directory and inboxes.
|
|
74
65
|
- **Cross-environment by design**: point `NEXUS_ENDPOINT` at the same daemon from different machines or shells. The daemon binds to localhost by default; use `--host 0.0.0.0` only on a trusted network.
|
|
75
66
|
|
|
76
|
-
## Quick
|
|
67
|
+
## 🚀 Quick start
|
|
68
|
+
|
|
69
|
+
Requires Node.js 22.14+ on the 22.x release line, or Node.js 23.6+.
|
|
77
70
|
|
|
78
71
|
### 1. Start the daemon
|
|
79
72
|
|
|
@@ -108,48 +101,26 @@ The adapter can start before the daemon. It loads the daemon tool list once the
|
|
|
108
101
|
| Windows IDE to WSL daemon | `http://<WSL-IP>:5688` |
|
|
109
102
|
| Remote machine | `http://<Server-IP>:5688` |
|
|
110
103
|
|
|
111
|
-
## Toolset
|
|
112
|
-
|
|
113
|
-
### Session and context
|
|
104
|
+
## 🛠️ Toolset
|
|
114
105
|
|
|
115
|
-
|
|
106
|
+
The daemon exposes 31 tools, grouped by purpose:
|
|
116
107
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- `send_message`: post meeting or global messages.
|
|
129
|
-
- `read_messages`: read unread messages incrementally.
|
|
130
|
-
- `update_global_strategy`: update the master strategy document.
|
|
131
|
-
- `sync_global_doc`: create or update shared docs.
|
|
132
|
-
|
|
133
|
-
### Meeting management
|
|
134
|
-
|
|
135
|
-
- `start_meeting`: open a meeting session.
|
|
136
|
-
- `end_meeting`: close and lock a meeting.
|
|
137
|
-
- `archive_meeting`: move a closed meeting to archive.
|
|
138
|
-
- `reopen_meeting`: reactivate a closed or archived meeting.
|
|
139
|
-
|
|
140
|
-
### Async task management
|
|
141
|
-
|
|
142
|
-
- `create_task`: create a background task.
|
|
143
|
-
- `get_task`: poll task status and result.
|
|
144
|
-
- `list_tasks`: list tasks by status.
|
|
145
|
-
- `cancel_task`: cancel a pending or running task.
|
|
108
|
+
| Group | Tools | Purpose |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| **Topology** | `get_global_topology` | Read the project list or a detailed per-project subgraph. |
|
|
111
|
+
| **Projects & assets** | `search_projects`, `get_project`, `update_project`, `rename_project`, `sync_project_assets`, `upload_project_asset` | Find, read, update, and rename projects; sync manifests and internal docs; upload binary assets. |
|
|
112
|
+
| **Global docs** | `list_global_docs`, `get_global_doc`, `sync_global_doc`, `get_global_strategy`, `update_global_strategy` | List, read, create and update shared global documents and the master strategy document. |
|
|
113
|
+
| **Messaging** | `send_message`, `read_messages`, `list_events` | Send direct, meeting or global messages; read shared messages and the activity log. |
|
|
114
|
+
| **Sessions and inboxes** | `list_sessions`, `update_session`, `read_inbox`, `get_message`, `acknowledge_message` | Discover connections, receive direct messages, and inspect explicit receipts and replies. |
|
|
115
|
+
| **Meetings** | `list_meetings`, `start_meeting`, `end_meeting`, `archive_meeting`, `reopen_meeting` | List, open, close, archive, and reopen meeting sessions. |
|
|
116
|
+
| **Tasks** | `create_task`, `get_task`, `list_tasks`, `cancel_task` | Create and track tasks that assistants manage themselves. |
|
|
117
|
+
| **Maintenance** | `host_maintenance`, `host_delete_project` | Prune or clear the global chat stream; delete a project. |
|
|
146
118
|
|
|
147
|
-
|
|
119
|
+
Direct inboxes are per MCP connection and require explicit reads; messages do not automatically wake models. Chats sharing one connection share its inbox. Receipt states distinguish storage, fetching, acknowledgement and replies; acknowledgement does not mean the requested work is complete. See the [assistant workflow](docs/ASSISTANT_GUIDE.md#direct-conversations).
|
|
148
120
|
|
|
149
|
-
|
|
150
|
-
- `host_delete_project`: delete a project and assets.
|
|
121
|
+
See [Tools reference](./docs/TOOLS_REFERENCE.md) for parameter schemas and call examples.
|
|
151
122
|
|
|
152
|
-
## Data
|
|
123
|
+
## 💾 Data storage
|
|
153
124
|
|
|
154
125
|
Default storage root:
|
|
155
126
|
|
|
@@ -173,10 +144,10 @@ Override with `--root <path>` or `NEXUS_ROOT`.
|
|
|
173
144
|
│ ├── internal_blueprint.md
|
|
174
145
|
│ └── assets/
|
|
175
146
|
├── registry.json
|
|
176
|
-
└── nexus.db
|
|
147
|
+
└── nexus.db # meetings, global chat, events, tasks, read cursors
|
|
177
148
|
```
|
|
178
149
|
|
|
179
|
-
## Project ID
|
|
150
|
+
## 🏷️ Project ID conventions
|
|
180
151
|
|
|
181
152
|
Project IDs follow `[prefix]_[name]`.
|
|
182
153
|
|
|
@@ -192,7 +163,7 @@ Project IDs follow `[prefix]_[name]`.
|
|
|
192
163
|
| `infra_` | Infrastructure / DevOps | `infra_k8s-config` |
|
|
193
164
|
| `doc_` | Documentation | `doc_coding-guide` |
|
|
194
165
|
|
|
195
|
-
## CLI
|
|
166
|
+
## 💻 CLI reference
|
|
196
167
|
|
|
197
168
|
```bash
|
|
198
169
|
# Start daemon
|
|
@@ -209,9 +180,13 @@ NEXUS_ENDPOINT=http://127.0.0.1:5688 n2n-nexus mcp
|
|
|
209
180
|
| `NEXUS_ENDPOINT` | Daemon URL for MCP adapter | `http://127.0.0.1:5688` |
|
|
210
181
|
| `NEXUS_ROOT` | Storage root for daemon | `~/.n2n-nexus` |
|
|
211
182
|
| `NEXUS_HOST` | Daemon bind host | `127.0.0.1` |
|
|
212
|
-
| `
|
|
183
|
+
| `NEXUS_DAEMON_PORT` | Daemon port (overridden by `--port`) | `5688` |
|
|
184
|
+
| `NEXUS_RATE_LIMIT` | Tool calls per second per instance; each client address gets 5x. `0` disables rate limiting | `20` |
|
|
185
|
+
| `NEXUS_RATE_BURST` | Calls an instance may make at once before the per-second limit applies (minimum 1) | `60` |
|
|
186
|
+
| `NEXUS_ALLOWED_HOSTS` | Extra DNS names accepted in the `Host` header (IP literals, `localhost`, `host.docker.internal` are always accepted) | empty |
|
|
187
|
+
| `NEXUS_INSTANCE_ID` | Override MCP instance ID (read cursors and meeting permissions are keyed on it) | `mcp-<hostname>-<hash of working directory>-<IDE name>` |
|
|
213
188
|
|
|
214
|
-
## Security and governance notes
|
|
189
|
+
## 🔐 Security and governance notes
|
|
215
190
|
|
|
216
191
|
- n2n-nexus stores coordination data locally by default.
|
|
217
192
|
- Do not put secrets, credentials, customer data, or private tokens in meeting messages or project assets unless your local policy allows it.
|
|
@@ -219,16 +194,26 @@ NEXUS_ENDPOINT=http://127.0.0.1:5688 n2n-nexus mcp
|
|
|
219
194
|
- Exposing the daemon beyond localhost is an operational choice; restrict network access when using remote endpoints.
|
|
220
195
|
- Treat project manifests and internal docs as potentially sensitive implementation data.
|
|
221
196
|
|
|
222
|
-
## Real-world example
|
|
197
|
+
## 🌐 Real-world example
|
|
223
198
|
|
|
224
|
-
The docs include a
|
|
199
|
+
The docs include a recorded multi-agent session that shows n2n-nexus in action. Four AI assistants — running in different IDEs and assigned to different projects — used a shared n2n-nexus daemon to coordinate in real time.
|
|
225
200
|
|
|
226
|
-
|
|
201
|
+
What to look for in the session:
|
|
202
|
+
|
|
203
|
+
- **Shared coordination space**: each assistant registered its active project (with the `register_session_context` tool of that release; current versions pass `projectId` explicitly instead), then read and wrote to the same daemon throughout the session.
|
|
204
|
+
- **Structured message categories**: `PROPOSAL`, `DECISION`, `UPDATE`, and `MEETING_START` tags let assistants signal intent, not just post text.
|
|
205
|
+
- **SYSTEM entries**: lines like `[Augment] Synced global doc: edge-sync-protocol-v1` record actual tool calls. That version wrote them into the chat stream; current versions keep them out of the conversation and expose them as structured events through `list_events`.
|
|
206
|
+
- **Async debugging across instances**: when one assistant hit an auth error, others responded with fixes in real time, each from their own IDE context.
|
|
207
|
+
- **Two output formats**: the discussion log is the raw chronological stream; the meeting minutes are a structured summary derived from it, showing how a long session distills into decisions and action items.
|
|
208
|
+
|
|
209
|
+
| File | What it shows |
|
|
227
210
|
| --- | --- |
|
|
228
|
-
| [
|
|
229
|
-
| [
|
|
211
|
+
| [Discussion Log](docs/discussion_2025-12-29.md) | The full chronological stream: proposals, debug exchanges, decisions, and SYSTEM tool entries |
|
|
212
|
+
| [Meeting Minutes](docs/MEETING_MINUTES_2025-12-29.md) | The structured output: participants, key decisions, test results, and action items |
|
|
230
213
|
|
|
231
|
-
## Local
|
|
214
|
+
## 🔧 Local development
|
|
215
|
+
|
|
216
|
+
Use Node.js 24 for development, matching CI and the lint toolchain.
|
|
232
217
|
|
|
233
218
|
```bash
|
|
234
219
|
git clone https://github.com/n2ns/n2n-nexus.git
|
|
@@ -243,44 +228,19 @@ node build/index.js daemon --root /tmp/nexus-test --port 5688
|
|
|
243
228
|
NEXUS_ENDPOINT=http://127.0.0.1:5688 node build/index.js mcp
|
|
244
229
|
```
|
|
245
230
|
|
|
246
|
-
##
|
|
247
|
-
|
|
248
|
-
### Is n2n-nexus a project management SaaS?
|
|
249
|
-
|
|
250
|
-
No. It is a local MCP coordination server for AI assistants. It stores state locally and exposes MCP tools to connected clients.
|
|
251
|
-
|
|
252
|
-
### Does it replace n2n-memory?
|
|
253
|
-
|
|
254
|
-
No. `n2n-memory` is repository-local memory for one project. `n2n-nexus` is a shared coordination hub for multiple assistants, project manifests, messages, meetings, and tasks.
|
|
255
|
-
|
|
256
|
-
### Does it work with Claude Desktop, Cursor, VS Code, and other IDEs?
|
|
257
|
-
|
|
258
|
-
Yes, when the client supports local MCP command servers. The adapter is started by the IDE and connects to the local daemon through `NEXUS_ENDPOINT`.
|
|
259
|
-
|
|
260
|
-
### Can it coordinate assistants across Windows and WSL?
|
|
261
|
-
|
|
262
|
-
Yes. Run the daemon in one environment and point each adapter at it using `NEXUS_ENDPOINT`.
|
|
263
|
-
|
|
264
|
-
### Does it send data to the cloud?
|
|
265
|
-
|
|
266
|
-
No cloud service is required. Data is stored under the configured local root. If you expose the daemon to a remote machine, that is your own network configuration.
|
|
267
|
-
|
|
268
|
-
### Is this a vector database or code indexer?
|
|
269
|
-
|
|
270
|
-
No. n2n-nexus coordinates project metadata, messages, meetings, docs, tasks, and assets. It is not a semantic code search engine.
|
|
271
|
-
|
|
272
|
-
## Related docs
|
|
231
|
+
## 📖 Related docs
|
|
273
232
|
|
|
274
|
-
- [Architecture](./docs/ARCHITECTURE.md)
|
|
275
|
-
- [
|
|
276
|
-
- [
|
|
277
|
-
- [Changelog](./CHANGELOG.md)
|
|
278
|
-
- [
|
|
233
|
+
- **[Architecture](./docs/ARCHITECTURE.md)**: System design and daemon-adapter separation explained.
|
|
234
|
+
- **[Tools reference](./docs/TOOLS_REFERENCE.md)**: Full tool list and parameters.
|
|
235
|
+
- **[AI Assistant Guide](./docs/ASSISTANT_GUIDE.md)**: How AI assistants should call tools and stay in project context.
|
|
236
|
+
- **[Changelog](./CHANGELOG.md)**: Version history and release notes.
|
|
237
|
+
- **[Contributing](./CONTRIBUTING.md)**: How to report issues and contribute.
|
|
238
|
+
- **[Security](./SECURITY.md)**: How to report vulnerabilities.
|
|
279
239
|
|
|
280
|
-
## License
|
|
240
|
+
## 📄 License
|
|
281
241
|
|
|
282
242
|
This project is licensed under the [Apache-2.0 License](./LICENSE).
|
|
283
243
|
|
|
284
244
|
---
|
|
285
245
|
|
|
286
|
-
Built by N2NS Lab,
|
|
246
|
+
Built by [N2NS Lab](https://n2ns.com), the open-source lab of [datafrog.io](https://datafrog.io) for practical AI developer tools.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
|
+
/** The daemon could not be reached or answered with something that is not JSON. */
|
|
5
|
+
export class NexusTransportError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "NexusTransportError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** The daemon answered, but the tool call itself failed (unknown tool, validation, not found, ...). */
|
|
12
|
+
export class NexusToolError extends Error {
|
|
13
|
+
status;
|
|
14
|
+
constructor(message, status) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.name = "NexusToolError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
4
20
|
function normalizeEndpoint(endpoint) {
|
|
5
21
|
const raw = endpoint || process.env.NEXUS_ENDPOINT || "http://127.0.0.1:5688";
|
|
6
22
|
if (/^https?:\/\//i.test(raw))
|
|
@@ -19,12 +35,8 @@ export class NexusClient {
|
|
|
19
35
|
const payload = body !== undefined ? JSON.stringify(body) : undefined;
|
|
20
36
|
const transport = url.protocol === "https:" ? https : http;
|
|
21
37
|
return new Promise((resolve, reject) => {
|
|
22
|
-
const req = transport.request({
|
|
38
|
+
const req = transport.request(url, {
|
|
23
39
|
method,
|
|
24
|
-
protocol: url.protocol,
|
|
25
|
-
hostname: url.hostname,
|
|
26
|
-
port: url.port,
|
|
27
|
-
path: `${url.pathname}${url.search}`,
|
|
28
40
|
headers: {
|
|
29
41
|
"Content-Type": "application/json",
|
|
30
42
|
...(payload ? { "Content-Length": Buffer.byteLength(payload) } : {})
|
|
@@ -32,40 +44,51 @@ export class NexusClient {
|
|
|
32
44
|
timeout: this.timeoutMs
|
|
33
45
|
}, (res) => {
|
|
34
46
|
let data = "";
|
|
47
|
+
const interrupted = () => reject(new NexusTransportError("Response from daemon ended before completion"));
|
|
48
|
+
res.on("aborted", interrupted);
|
|
49
|
+
res.on("error", (error) => reject(new NexusTransportError(error.message)));
|
|
50
|
+
res.on("close", () => { if (!res.complete)
|
|
51
|
+
interrupted(); });
|
|
35
52
|
res.on("data", chunk => { data += chunk; });
|
|
36
53
|
res.on("end", () => {
|
|
54
|
+
let parsed;
|
|
37
55
|
try {
|
|
38
|
-
|
|
39
|
-
if (res.statusCode && res.statusCode >= 400) {
|
|
40
|
-
reject(new Error(parsed.error || `HTTP ${res.statusCode}`));
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
resolve(parsed);
|
|
44
|
-
}
|
|
56
|
+
parsed = data ? JSON.parse(data) : {};
|
|
45
57
|
}
|
|
46
58
|
catch {
|
|
47
|
-
reject(new
|
|
59
|
+
reject(new NexusTransportError(`Invalid JSON response from daemon: ${data.slice(0, 200)}`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const status = res.statusCode ?? 0;
|
|
63
|
+
if (status >= 400) {
|
|
64
|
+
reject(new NexusToolError(parsed.error || `HTTP ${status}`, status));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
resolve(parsed);
|
|
48
68
|
}
|
|
49
69
|
});
|
|
50
70
|
});
|
|
51
|
-
req.on("error", reject);
|
|
52
|
-
req.on("timeout", () => req.destroy(new
|
|
71
|
+
req.on("error", (e) => reject(e instanceof NexusTransportError ? e : new NexusTransportError(e.message)));
|
|
72
|
+
req.on("timeout", () => req.destroy(new NexusTransportError(`Request to ${this.endpoint} timed out after ${this.timeoutMs} ms`)));
|
|
53
73
|
if (payload)
|
|
54
74
|
req.write(payload);
|
|
55
75
|
req.end();
|
|
56
76
|
});
|
|
57
77
|
}
|
|
58
|
-
async health() {
|
|
59
|
-
return this.request("GET", "/health");
|
|
60
|
-
}
|
|
61
78
|
async fetchTools() {
|
|
62
79
|
const res = await this.request("GET", "/api/tools");
|
|
63
80
|
return res.tools;
|
|
64
81
|
}
|
|
65
|
-
async
|
|
66
|
-
|
|
82
|
+
async registerSession(session) {
|
|
83
|
+
await this.request("POST", "/api/sessions/register", session);
|
|
84
|
+
}
|
|
85
|
+
async disconnectSession(sessionId, instanceId) {
|
|
86
|
+
await this.request("POST", "/api/sessions/disconnect", { sessionId, instanceId });
|
|
87
|
+
}
|
|
88
|
+
async callTool(name, args, instanceId, sessionId) {
|
|
89
|
+
const res = await this.request("POST", "/api/tools/call", { tool: name, args: args || {}, instanceId: instanceId || "unknown", ...(sessionId ? { sessionId } : {}) });
|
|
67
90
|
if (!res.ok)
|
|
68
|
-
throw new
|
|
91
|
+
throw new NexusToolError(res.error || "Tool call failed", 200);
|
|
69
92
|
return res.result;
|
|
70
93
|
}
|
|
71
94
|
}
|
package/build/config/cli.js
CHANGED
package/build/config/index.js
CHANGED
|
@@ -4,13 +4,3 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
const pkgPath = path.resolve(__dirname, "../../package.json");
|
|
6
6
|
export const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
7
|
-
export function getRootPath() {
|
|
8
|
-
// Priority: --root CLI arg → NEXUS_ROOT env → ~/.n2n-nexus
|
|
9
|
-
const argIndex = process.argv.indexOf("--root");
|
|
10
|
-
if (argIndex !== -1 && process.argv[argIndex + 1]) {
|
|
11
|
-
return process.argv[argIndex + 1];
|
|
12
|
-
}
|
|
13
|
-
if (process.env.NEXUS_ROOT)
|
|
14
|
-
return process.env.NEXUS_ROOT;
|
|
15
|
-
return path.join(process.env.HOME || process.env.USERPROFILE || ".", ".n2n-nexus");
|
|
16
|
-
}
|
package/build/constants.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Nexus
|
|
3
|
-
*
|
|
4
|
-
* Centralized configuration for network-related settings.
|
|
2
|
+
* Nexus constants
|
|
5
3
|
*/
|
|
6
4
|
// Service identification
|
|
7
5
|
export const SERVICE_NAME = "n2n-nexus";
|
|
@@ -9,14 +7,12 @@ export const SERVICE_NAME = "n2n-nexus";
|
|
|
9
7
|
// Default to localhost because the daemon exposes unauthenticated local admin APIs.
|
|
10
8
|
// Use "0.0.0.0" only when you intentionally expose it to another trusted environment.
|
|
11
9
|
export const NEXUS_HOST = "127.0.0.1";
|
|
12
|
-
//
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export const HEARTBEAT_INTERVAL = 30000;
|
|
18
|
-
// Task cleanup
|
|
10
|
+
// Default daemon port
|
|
11
|
+
export const NEXUS_PORT = 5688;
|
|
12
|
+
// HTTP request body limit for the daemon (base64 asset uploads are the largest payload)
|
|
13
|
+
export const MAX_BODY_BYTES = 16 * 1024 * 1024;
|
|
14
|
+
// Task lifecycle: finished tasks are purged after this age; maintenance runs at this interval
|
|
19
15
|
export const TASK_CLEANUP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
16
|
+
export const TASK_MAINTENANCE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
20
17
|
// File I/O
|
|
21
18
|
export const FILE_ENCODING = "utf-8";
|
|
22
|
-
export const PACKAGE_JSON = "package.json";
|