@virsanghavi/axis-server 1.0.0 → 1.0.2
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 +1 -1
- package/dist/mcp-server.mjs +115 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @virsanghavi/axis-server
|
|
2
2
|
|
|
3
|
-
The official Axis
|
|
3
|
+
The official Axis Parallel Orchestration MCP Server. This server enables distributed coordination and shared memory for parallel agent workflows via the Model Context Protocol (MCP).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// ../../src/local/mcp-server.ts
|
|
2
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -13,7 +20,16 @@ import dotenv2 from "dotenv";
|
|
|
13
20
|
import fs from "fs/promises";
|
|
14
21
|
import path from "path";
|
|
15
22
|
import { Mutex } from "async-mutex";
|
|
16
|
-
var
|
|
23
|
+
var LEGACY_INSTRUCTIONS_DIR = path.resolve(process.cwd(), "agent-instructions");
|
|
24
|
+
var AXIS_DIR = path.resolve(process.cwd(), ".axis");
|
|
25
|
+
var INSTRUCTIONS_DIR = path.resolve(AXIS_DIR, "instructions");
|
|
26
|
+
function getEffectiveInstructionsDir() {
|
|
27
|
+
try {
|
|
28
|
+
if (__require("fs").existsSync(INSTRUCTIONS_DIR)) return INSTRUCTIONS_DIR;
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
return LEGACY_INSTRUCTIONS_DIR;
|
|
32
|
+
}
|
|
17
33
|
var ContextManager = class {
|
|
18
34
|
mutex;
|
|
19
35
|
apiUrl;
|
|
@@ -27,15 +43,22 @@ var ContextManager = class {
|
|
|
27
43
|
if (!filename || filename.includes("\0")) {
|
|
28
44
|
throw new Error("Invalid filename");
|
|
29
45
|
}
|
|
30
|
-
const resolved = path.resolve(
|
|
31
|
-
|
|
46
|
+
const resolved = path.resolve(getEffectiveInstructionsDir(), filename);
|
|
47
|
+
const effectiveDir = getEffectiveInstructionsDir();
|
|
48
|
+
if (!resolved.startsWith(effectiveDir + path.sep)) {
|
|
32
49
|
throw new Error("Invalid file path");
|
|
33
50
|
}
|
|
34
51
|
return resolved;
|
|
35
52
|
}
|
|
36
53
|
async listFiles() {
|
|
37
54
|
try {
|
|
38
|
-
const
|
|
55
|
+
const dir = getEffectiveInstructionsDir();
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(dir);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const files = await fs.readdir(dir);
|
|
39
62
|
const docFiles = await this.listDocs();
|
|
40
63
|
const instructionFiles = files.filter((f) => f.endsWith(".md")).map((f) => ({
|
|
41
64
|
uri: `context://local/${f}`,
|
|
@@ -214,10 +237,23 @@ var NerveCenter = class {
|
|
|
214
237
|
}
|
|
215
238
|
async init() {
|
|
216
239
|
await this.loadState();
|
|
240
|
+
await this.detectProjectName();
|
|
217
241
|
if (this.useSupabase) {
|
|
218
242
|
await this.ensureProjectId();
|
|
219
243
|
}
|
|
220
244
|
}
|
|
245
|
+
async detectProjectName() {
|
|
246
|
+
try {
|
|
247
|
+
const axisConfigPath = path2.join(process.cwd(), ".axis", "axis.json");
|
|
248
|
+
const configData = await fs2.readFile(axisConfigPath, "utf-8");
|
|
249
|
+
const config = JSON.parse(configData);
|
|
250
|
+
if (config.project) {
|
|
251
|
+
this.projectName = config.project;
|
|
252
|
+
logger.info(`Detected project name from .axis/axis.json: ${this.projectName}`);
|
|
253
|
+
}
|
|
254
|
+
} catch (e) {
|
|
255
|
+
}
|
|
256
|
+
}
|
|
221
257
|
async ensureProjectId() {
|
|
222
258
|
if (!this.supabase) return;
|
|
223
259
|
const { data: project, error } = await this.supabase.from("projects").select("id").eq("name", this.projectName).maybeSingle();
|
|
@@ -284,6 +320,32 @@ var NerveCenter = class {
|
|
|
284
320
|
return Object.values(this.state.locks);
|
|
285
321
|
}
|
|
286
322
|
}
|
|
323
|
+
async getNotepad() {
|
|
324
|
+
if (!this.useSupabase || !this.supabase || !this._projectId) {
|
|
325
|
+
return this.state.liveNotepad;
|
|
326
|
+
}
|
|
327
|
+
const { data, error } = await this.supabase.from("projects").select("live_notepad").eq("id", this._projectId).single();
|
|
328
|
+
if (error || !data) {
|
|
329
|
+
logger.error("Failed to fetch notepad", error);
|
|
330
|
+
return this.state.liveNotepad;
|
|
331
|
+
}
|
|
332
|
+
return data.live_notepad || "";
|
|
333
|
+
}
|
|
334
|
+
async appendToNotepad(text) {
|
|
335
|
+
if (!this.useSupabase || !this.supabase || !this._projectId) {
|
|
336
|
+
this.state.liveNotepad += text;
|
|
337
|
+
await this.saveState();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const { error } = await this.supabase.rpc("append_to_project_notepad", {
|
|
341
|
+
p_project_id: this._projectId,
|
|
342
|
+
p_text: text
|
|
343
|
+
});
|
|
344
|
+
if (error) {
|
|
345
|
+
const current = await this.getNotepad();
|
|
346
|
+
await this.supabase.from("projects").update({ live_notepad: current + text }).eq("id", this._projectId);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
287
349
|
async saveState() {
|
|
288
350
|
try {
|
|
289
351
|
await fs2.mkdir(path2.dirname(this.stateFilePath), { recursive: true });
|
|
@@ -335,10 +397,10 @@ var NerveCenter = class {
|
|
|
335
397
|
};
|
|
336
398
|
}
|
|
337
399
|
const depText = dependencies.length ? ` (Depends on: ${dependencies.join(", ")})` : "";
|
|
338
|
-
|
|
400
|
+
const logEntry = `
|
|
339
401
|
- [JOB POSTED] [${priority.toUpperCase()}] ${title} (ID: ${id})${depText}`;
|
|
402
|
+
await this.appendToNotepad(logEntry);
|
|
340
403
|
logger.info(`Job posted: ${title}`, { jobId: id, priority });
|
|
341
|
-
await this.saveState();
|
|
342
404
|
return { jobId: id, status: "POSTED" };
|
|
343
405
|
});
|
|
344
406
|
}
|
|
@@ -373,10 +435,9 @@ var NerveCenter = class {
|
|
|
373
435
|
}
|
|
374
436
|
if (data && data.length > 0) {
|
|
375
437
|
const job2 = this.jobFromRecord(data[0]);
|
|
376
|
-
this.
|
|
377
|
-
- [JOB CLAIMED] Agent '${agentId}' picked up: ${job2.title}
|
|
438
|
+
await this.appendToNotepad(`
|
|
439
|
+
- [JOB CLAIMED] Agent '${agentId}' picked up: ${job2.title}`);
|
|
378
440
|
logger.info(`Job claimed`, { jobId: job2.id, agentId });
|
|
379
|
-
await this.saveState();
|
|
380
441
|
return { status: "CLAIMED", job: job2 };
|
|
381
442
|
}
|
|
382
443
|
}
|
|
@@ -442,10 +503,9 @@ var NerveCenter = class {
|
|
|
442
503
|
if (data.assigned_to !== agentId) return { error: "You don't own this job." };
|
|
443
504
|
const { error: updateError } = await this.supabase.from("jobs").update({ status: "done", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", jobId).eq("assigned_to", agentId);
|
|
444
505
|
if (updateError) return { error: "Failed to complete job" };
|
|
445
|
-
this.
|
|
446
|
-
- [JOB DONE] ${data.title} by ${agentId}. Outcome: ${outcome}
|
|
506
|
+
await this.appendToNotepad(`
|
|
507
|
+
- [JOB DONE] ${data.title} by ${agentId}. Outcome: ${outcome}`);
|
|
447
508
|
logger.info(`Job completed`, { jobId, agentId });
|
|
448
|
-
await this.saveState();
|
|
449
509
|
return { status: "COMPLETED" };
|
|
450
510
|
}
|
|
451
511
|
const job = this.state.jobs[jobId];
|
|
@@ -472,6 +532,7 @@ var NerveCenter = class {
|
|
|
472
532
|
(j) => `- [${j.status.toUpperCase()}] ${j.title} ${j.assignedTo ? "(" + j.assignedTo + ")" : "(Open)"}
|
|
473
533
|
ID: ${j.id}`
|
|
474
534
|
).join("\n");
|
|
535
|
+
const notepad = await this.getNotepad();
|
|
475
536
|
return `# Active Session Context
|
|
476
537
|
|
|
477
538
|
## Job Board (Active Orchestration)
|
|
@@ -481,7 +542,7 @@ ${jobSummary || "No active jobs."}
|
|
|
481
542
|
${lockSummary || "No active locks."}
|
|
482
543
|
|
|
483
544
|
## Live Notepad
|
|
484
|
-
${
|
|
545
|
+
${notepad}`;
|
|
485
546
|
}
|
|
486
547
|
// --- Decision & Orchestration ---
|
|
487
548
|
async proposeFileAccess(agentId, filePath, intent, userPrompt) {
|
|
@@ -515,35 +576,40 @@ ${this.state.liveNotepad}`;
|
|
|
515
576
|
logger.error("Lock upsert failed", error);
|
|
516
577
|
return { status: "ERROR", message: "Database lock failed." };
|
|
517
578
|
}
|
|
518
|
-
this.
|
|
579
|
+
await this.appendToNotepad(`
|
|
519
580
|
|
|
520
581
|
### [${agentId}] Locked '${filePath}'
|
|
521
582
|
**Intent:** ${intent}
|
|
522
|
-
**Prompt:** "${userPrompt}"
|
|
523
|
-
await this.saveState();
|
|
583
|
+
**Prompt:** "${userPrompt}"`);
|
|
524
584
|
return { status: "GRANTED", message: `Access granted for ${filePath}` };
|
|
525
585
|
});
|
|
526
586
|
}
|
|
527
587
|
async updateSharedContext(text, agentId) {
|
|
528
588
|
return await this.mutex.runExclusive(async () => {
|
|
529
|
-
this.
|
|
530
|
-
- [${agentId}] ${text}
|
|
531
|
-
await this.saveState();
|
|
589
|
+
await this.appendToNotepad(`
|
|
590
|
+
- [${agentId}] ${text}`);
|
|
532
591
|
return "Notepad updated.";
|
|
533
592
|
});
|
|
534
593
|
}
|
|
535
594
|
async finalizeSession() {
|
|
536
595
|
return await this.mutex.runExclusive(async () => {
|
|
537
|
-
const content = this.
|
|
596
|
+
const content = await this.getNotepad();
|
|
538
597
|
const filename = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`;
|
|
539
598
|
const historyPath = path2.join(process.cwd(), "history", filename);
|
|
540
599
|
await fs2.writeFile(historyPath, content);
|
|
541
|
-
this.state.liveNotepad = "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
|
|
542
|
-
this.state.locks = {};
|
|
543
600
|
if (this.useSupabase && this.supabase && this._projectId) {
|
|
601
|
+
await this.supabase.from("sessions").insert({
|
|
602
|
+
project_id: this._projectId,
|
|
603
|
+
title: `Session ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
|
|
604
|
+
summary: content.substring(0, 500) + "...",
|
|
605
|
+
metadata: { full_content: content }
|
|
606
|
+
});
|
|
607
|
+
await this.supabase.from("projects").update({ live_notepad: "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n" }).eq("id", this._projectId);
|
|
544
608
|
await this.supabase.from("jobs").delete().eq("project_id", this._projectId).in("status", ["done", "cancelled"]);
|
|
545
609
|
await this.supabase.from("locks").delete().eq("project_id", this._projectId);
|
|
546
610
|
} else {
|
|
611
|
+
this.state.liveNotepad = "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
|
|
612
|
+
this.state.locks = {};
|
|
547
613
|
this.state.jobs = Object.fromEntries(
|
|
548
614
|
Object.entries(this.state.jobs).filter(([_, j]) => j.status !== "done" && j.status !== "cancelled")
|
|
549
615
|
);
|
|
@@ -682,8 +748,8 @@ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_
|
|
|
682
748
|
process.exit(1);
|
|
683
749
|
}
|
|
684
750
|
var manager = new ContextManager(
|
|
685
|
-
process.env.SHARED_CONTEXT_API_URL,
|
|
686
|
-
process.env.SHARED_CONTEXT_API_SECRET
|
|
751
|
+
process.env.SHARED_CONTEXT_API_URL || "https://aicontext.vercel.app/api/v1",
|
|
752
|
+
process.env.AXIS_API_KEY || process.env.SHARED_CONTEXT_API_SECRET
|
|
687
753
|
);
|
|
688
754
|
var nerveCenter = new NerveCenter(manager, {
|
|
689
755
|
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
@@ -697,21 +763,34 @@ var ragEngine = new RagEngine(
|
|
|
697
763
|
// Project ID is loaded async by NerveCenter... tricky dependency.
|
|
698
764
|
// We'll let NerveCenter expose it or pass it later.
|
|
699
765
|
);
|
|
700
|
-
var REQUIRED_DIRS = ["agent-instructions", "history"];
|
|
701
766
|
async function ensureFileSystem() {
|
|
702
767
|
const fs3 = await import("fs/promises");
|
|
703
768
|
const path3 = await import("path");
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
769
|
+
const fsSync = await import("fs");
|
|
770
|
+
const cwd = process.cwd();
|
|
771
|
+
const historyDir = path3.join(cwd, "history");
|
|
772
|
+
await fs3.mkdir(historyDir, { recursive: true }).catch(() => {
|
|
773
|
+
});
|
|
774
|
+
const axisDir = path3.join(cwd, ".axis");
|
|
775
|
+
const axisInstructions = path3.join(axisDir, "instructions");
|
|
776
|
+
const legacyInstructions = path3.join(cwd, "agent-instructions");
|
|
777
|
+
if (fsSync.existsSync(legacyInstructions) && !fsSync.existsSync(axisDir)) {
|
|
778
|
+
logger.info("Using legacy agent-instructions directory");
|
|
779
|
+
} else {
|
|
780
|
+
await fs3.mkdir(axisInstructions, { recursive: true }).catch(() => {
|
|
781
|
+
});
|
|
782
|
+
const defaults = [
|
|
783
|
+
["context.md", "# Project Context\n\n"],
|
|
784
|
+
["conventions.md", "# Coding Conventions\n\n"],
|
|
785
|
+
["activity.md", "# Activity Log\n\n"]
|
|
786
|
+
];
|
|
787
|
+
for (const [file, content] of defaults) {
|
|
788
|
+
const p = path3.join(axisInstructions, file);
|
|
789
|
+
try {
|
|
790
|
+
await fs3.access(p);
|
|
791
|
+
} catch {
|
|
792
|
+
await fs3.writeFile(p, content);
|
|
793
|
+
logger.info(`Created default context file: ${file}`);
|
|
715
794
|
}
|
|
716
795
|
}
|
|
717
796
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@virsanghavi/axis-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Axis MCP Server CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"axis-server": "
|
|
7
|
+
"axis-server": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node dist/cli.js",
|