@virsanghavi/axis-server 1.5.0 → 1.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/dist/mcp-server.mjs +142 -72
- package/package.json +1 -1
package/dist/mcp-server.mjs
CHANGED
|
@@ -1035,12 +1035,13 @@ ${conventions}`;
|
|
|
1035
1035
|
}
|
|
1036
1036
|
// --- Billing & Usage ---
|
|
1037
1037
|
async getSubscriptionStatus(email) {
|
|
1038
|
-
logger.info(`[getSubscriptionStatus] Starting - email: ${email}`);
|
|
1038
|
+
logger.info(`[getSubscriptionStatus] Starting - email: ${email || "(API key identity)"}`);
|
|
1039
1039
|
logger.info(`[getSubscriptionStatus] Config - apiUrl: ${this.contextManager.apiUrl}, apiSecret: ${this.contextManager.apiSecret ? "SET" : "NOT SET"}, useSupabase: ${this.useSupabase}`);
|
|
1040
1040
|
if (this.contextManager.apiUrl) {
|
|
1041
1041
|
try {
|
|
1042
|
-
|
|
1043
|
-
|
|
1042
|
+
const endpoint = email ? `usage?email=${encodeURIComponent(email)}` : "usage";
|
|
1043
|
+
logger.info(`[getSubscriptionStatus] Attempting API call to: ${endpoint}`);
|
|
1044
|
+
const result = await this.callCoordination(endpoint);
|
|
1044
1045
|
logger.info(`[getSubscriptionStatus] API call successful: ${JSON.stringify(result).substring(0, 200)}`);
|
|
1045
1046
|
return result;
|
|
1046
1047
|
} catch (e) {
|
|
@@ -1050,8 +1051,8 @@ ${conventions}`;
|
|
|
1050
1051
|
} else {
|
|
1051
1052
|
logger.warn("[getSubscriptionStatus] No API URL configured");
|
|
1052
1053
|
}
|
|
1053
|
-
if (this.useSupabase && this.supabase) {
|
|
1054
|
-
const { data: profile, error } = await this.supabase.from("profiles").select("subscription_status, stripe_customer_id, current_period_end").
|
|
1054
|
+
if (this.useSupabase && this.supabase && email) {
|
|
1055
|
+
const { data: profile, error } = await this.supabase.from("profiles").select("subscription_status, stripe_customer_id, current_period_end").ilike("email", email).single();
|
|
1055
1056
|
if (error || !profile) {
|
|
1056
1057
|
return { status: "unknown", message: "Profile not found." };
|
|
1057
1058
|
}
|
|
@@ -1066,21 +1067,22 @@ ${conventions}`;
|
|
|
1066
1067
|
return { error: "Coordination not configured. API URL not set and Supabase not available." };
|
|
1067
1068
|
}
|
|
1068
1069
|
async getUsageStats(email) {
|
|
1069
|
-
logger.info(`[getUsageStats] Starting - email: ${email}`);
|
|
1070
|
+
logger.info(`[getUsageStats] Starting - email: ${email || "(API key identity)"}`);
|
|
1070
1071
|
logger.info(`[getUsageStats] Config - apiUrl: ${this.contextManager.apiUrl}, apiSecret: ${this.contextManager.apiSecret ? "SET" : "NOT SET"}, useSupabase: ${this.useSupabase}`);
|
|
1071
1072
|
if (this.contextManager.apiUrl) {
|
|
1072
1073
|
try {
|
|
1073
|
-
|
|
1074
|
-
|
|
1074
|
+
const endpoint = email ? `usage?email=${encodeURIComponent(email)}` : "usage";
|
|
1075
|
+
logger.info(`[getUsageStats] Attempting API call to: ${endpoint}`);
|
|
1076
|
+
const result = await this.callCoordination(endpoint);
|
|
1075
1077
|
logger.info(`[getUsageStats] API call successful: ${JSON.stringify(result).substring(0, 200)}`);
|
|
1076
|
-
return { email, usageCount: result.usageCount || 0 };
|
|
1078
|
+
return { email: email || result.email, usageCount: result.usageCount || 0 };
|
|
1077
1079
|
} catch (e) {
|
|
1078
1080
|
logger.error(`[getUsageStats] API call failed: ${e.message}`, e);
|
|
1079
1081
|
return { error: `API call failed: ${e.message}` };
|
|
1080
1082
|
}
|
|
1081
1083
|
}
|
|
1082
|
-
if (this.useSupabase && this.supabase) {
|
|
1083
|
-
const { data: profile } = await this.supabase.from("profiles").select("usage_count").
|
|
1084
|
+
if (this.useSupabase && this.supabase && email) {
|
|
1085
|
+
const { data: profile } = await this.supabase.from("profiles").select("usage_count").ilike("email", email).single();
|
|
1084
1086
|
return { email, usageCount: profile?.usage_count || 0 };
|
|
1085
1087
|
}
|
|
1086
1088
|
return { error: "Coordination not configured. API URL not set and Supabase not available." };
|
|
@@ -1166,6 +1168,7 @@ import fs4 from "fs";
|
|
|
1166
1168
|
|
|
1167
1169
|
// ../../src/local/local-search.ts
|
|
1168
1170
|
import fs3 from "fs/promises";
|
|
1171
|
+
import fsSync2 from "fs";
|
|
1169
1172
|
import path3 from "path";
|
|
1170
1173
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1171
1174
|
"node_modules",
|
|
@@ -1321,34 +1324,61 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
1321
1324
|
"most",
|
|
1322
1325
|
"some",
|
|
1323
1326
|
"any",
|
|
1324
|
-
"find",
|
|
1325
|
-
"show",
|
|
1326
|
-
"get",
|
|
1327
|
-
"look",
|
|
1328
|
-
"search",
|
|
1329
|
-
"locate",
|
|
1330
|
-
"check",
|
|
1331
|
-
"file",
|
|
1332
|
-
"files",
|
|
1333
|
-
"code",
|
|
1334
|
-
"function",
|
|
1335
|
-
"class",
|
|
1336
|
-
"method",
|
|
1337
1327
|
"there",
|
|
1338
1328
|
"here",
|
|
1339
1329
|
"just",
|
|
1340
1330
|
"also",
|
|
1341
1331
|
"very",
|
|
1342
1332
|
"really",
|
|
1343
|
-
"quite"
|
|
1333
|
+
"quite",
|
|
1334
|
+
"show",
|
|
1335
|
+
"look",
|
|
1336
|
+
"locate",
|
|
1337
|
+
"using",
|
|
1338
|
+
"used",
|
|
1339
|
+
"need",
|
|
1340
|
+
"want"
|
|
1344
1341
|
]);
|
|
1345
1342
|
var MAX_FILE_SIZE = 256 * 1024;
|
|
1346
1343
|
var MAX_RESULTS = 20;
|
|
1347
1344
|
var CONTEXT_LINES = 2;
|
|
1348
1345
|
var MAX_MATCHES_PER_FILE = 6;
|
|
1349
1346
|
function extractKeywords(query) {
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1347
|
+
const words = query.toLowerCase().replace(/[^\w\s\-_.]/g, " ").split(/\s+/).filter((w) => w.length >= 2);
|
|
1348
|
+
const filtered = words.filter((w) => !STOP_WORDS.has(w));
|
|
1349
|
+
const result = filtered.length > 0 ? filtered : words.filter((w) => w.length >= 3);
|
|
1350
|
+
return [...new Set(result)];
|
|
1351
|
+
}
|
|
1352
|
+
var PROJECT_ROOT_MARKERS = [
|
|
1353
|
+
".git",
|
|
1354
|
+
".axis",
|
|
1355
|
+
"package.json",
|
|
1356
|
+
"Cargo.toml",
|
|
1357
|
+
"go.mod",
|
|
1358
|
+
"pyproject.toml",
|
|
1359
|
+
"setup.py",
|
|
1360
|
+
"Gemfile",
|
|
1361
|
+
"pom.xml",
|
|
1362
|
+
"tsconfig.json",
|
|
1363
|
+
".cursorrules",
|
|
1364
|
+
"AGENTS.md"
|
|
1365
|
+
];
|
|
1366
|
+
function detectProjectRoot(startDir) {
|
|
1367
|
+
let current = startDir;
|
|
1368
|
+
const root = path3.parse(current).root;
|
|
1369
|
+
while (current !== root) {
|
|
1370
|
+
for (const marker of PROJECT_ROOT_MARKERS) {
|
|
1371
|
+
try {
|
|
1372
|
+
fsSync2.accessSync(path3.join(current, marker));
|
|
1373
|
+
return current;
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const parent = path3.dirname(current);
|
|
1378
|
+
if (parent === current) break;
|
|
1379
|
+
current = parent;
|
|
1380
|
+
}
|
|
1381
|
+
return startDir;
|
|
1352
1382
|
}
|
|
1353
1383
|
async function walkDir(dir, maxDepth = 12) {
|
|
1354
1384
|
const results = [];
|
|
@@ -1428,8 +1458,12 @@ async function searchFile(filePath, rootDir, keywords) {
|
|
|
1428
1458
|
return { filePath, relativePath, score, matchedKeywords, regions };
|
|
1429
1459
|
}
|
|
1430
1460
|
async function localSearch(query, rootDir) {
|
|
1431
|
-
const
|
|
1461
|
+
const rawCwd = rootDir || process.cwd();
|
|
1462
|
+
const cwd = detectProjectRoot(rawCwd);
|
|
1432
1463
|
const keywords = extractKeywords(query);
|
|
1464
|
+
if (cwd !== rawCwd) {
|
|
1465
|
+
logger.info(`[localSearch] Detected project root: ${cwd} (CWD was: ${rawCwd})`);
|
|
1466
|
+
}
|
|
1433
1467
|
if (keywords.length === 0) {
|
|
1434
1468
|
return "Could not extract meaningful search terms from the query. Try being more specific (e.g. 'authentication middleware' instead of 'how does it work').";
|
|
1435
1469
|
}
|
|
@@ -1557,8 +1591,20 @@ var subscription = {
|
|
|
1557
1591
|
};
|
|
1558
1592
|
async function verifySubscription() {
|
|
1559
1593
|
if (!apiSecret) {
|
|
1560
|
-
|
|
1561
|
-
|
|
1594
|
+
const hasDirectSupabase = !useRemoteApiOnly && !!process.env.NEXT_PUBLIC_SUPABASE_URL && !!process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
1595
|
+
if (hasDirectSupabase) {
|
|
1596
|
+
subscription = { checked: true, valid: true, plan: "developer", reason: "Direct Supabase mode \u2014 no API key needed", checkedAt: Date.now() };
|
|
1597
|
+
logger.info("[subscription] Direct Supabase credentials found \u2014 developer mode, skipping verification");
|
|
1598
|
+
return subscription;
|
|
1599
|
+
}
|
|
1600
|
+
subscription = {
|
|
1601
|
+
checked: true,
|
|
1602
|
+
valid: false,
|
|
1603
|
+
plan: "none",
|
|
1604
|
+
reason: "no_api_key",
|
|
1605
|
+
checkedAt: Date.now()
|
|
1606
|
+
};
|
|
1607
|
+
logger.error("[subscription] No API key configured. Axis requires an API key from https://useaxis.dev/dashboard");
|
|
1562
1608
|
return subscription;
|
|
1563
1609
|
}
|
|
1564
1610
|
const verifyUrl = apiUrl.endsWith("/v1") ? `${apiUrl}/verify` : `${apiUrl}/v1/verify`;
|
|
@@ -1616,6 +1662,22 @@ function isSubscriptionStale() {
|
|
|
1616
1662
|
return Date.now() - subscription.checkedAt > RECHECK_INTERVAL_MS;
|
|
1617
1663
|
}
|
|
1618
1664
|
function getSubscriptionBlockMessage() {
|
|
1665
|
+
if (subscription.reason === "no_api_key") {
|
|
1666
|
+
return [
|
|
1667
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
1668
|
+
" Axis API key required",
|
|
1669
|
+
"",
|
|
1670
|
+
" No API key found. Axis requires an active subscription",
|
|
1671
|
+
" and a valid API key to operate.",
|
|
1672
|
+
"",
|
|
1673
|
+
" 1. Sign up or log in at https://useaxis.dev",
|
|
1674
|
+
" 2. Subscribe to Axis Pro",
|
|
1675
|
+
" 3. Generate an API key from the dashboard",
|
|
1676
|
+
" 4. Add AXIS_API_KEY to your mcp.json configuration",
|
|
1677
|
+
" 5. Restart your IDE",
|
|
1678
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
|
|
1679
|
+
].join("\n");
|
|
1680
|
+
}
|
|
1619
1681
|
return [
|
|
1620
1682
|
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
1621
1683
|
" Axis Pro subscription required",
|
|
@@ -1644,7 +1706,7 @@ async function ensureFileSystem() {
|
|
|
1644
1706
|
try {
|
|
1645
1707
|
const fs5 = await import("fs/promises");
|
|
1646
1708
|
const path5 = await import("path");
|
|
1647
|
-
const
|
|
1709
|
+
const fsSync3 = await import("fs");
|
|
1648
1710
|
const cwd = process.cwd();
|
|
1649
1711
|
logger.info(`Server CWD: ${cwd}`);
|
|
1650
1712
|
const historyDir = path5.join(cwd, "history");
|
|
@@ -1653,7 +1715,7 @@ async function ensureFileSystem() {
|
|
|
1653
1715
|
const axisDir = path5.join(cwd, ".axis");
|
|
1654
1716
|
const axisInstructions = path5.join(axisDir, "instructions");
|
|
1655
1717
|
const legacyInstructions = path5.join(cwd, "agent-instructions");
|
|
1656
|
-
if (
|
|
1718
|
+
if (fsSync3.existsSync(legacyInstructions) && !fsSync3.existsSync(axisDir)) {
|
|
1657
1719
|
logger.info("Using legacy agent-instructions directory");
|
|
1658
1720
|
} else {
|
|
1659
1721
|
await fs5.mkdir(axisInstructions, { recursive: true }).catch(() => {
|
|
@@ -1828,24 +1890,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1828
1890
|
// --- Billing & Usage ---
|
|
1829
1891
|
{
|
|
1830
1892
|
name: "get_subscription_status",
|
|
1831
|
-
description: "**BILLING CHECK**:
|
|
1893
|
+
description: "**BILLING CHECK**: Returns the user's subscription tier (Pro vs Free), Stripe customer ID, and current period end.\n- If no email is provided, returns the subscription status of the current API key owner.\n- Critical for gating features behind paywalls.",
|
|
1832
1894
|
inputSchema: {
|
|
1833
1895
|
type: "object",
|
|
1834
1896
|
properties: {
|
|
1835
|
-
email: { type: "string", description: "User email to check." }
|
|
1836
|
-
}
|
|
1837
|
-
required: ["email"]
|
|
1897
|
+
email: { type: "string", description: "Optional. User email to check. If omitted, checks the subscription of the current API key owner." }
|
|
1898
|
+
}
|
|
1838
1899
|
}
|
|
1839
1900
|
},
|
|
1840
1901
|
{
|
|
1841
1902
|
name: "get_usage_stats",
|
|
1842
|
-
description: "**API USAGE**: Returns
|
|
1903
|
+
description: "**API USAGE**: Returns token usage and request counts.\n- If no email is provided, returns usage for the current API key owner.\n- Useful for debugging rate limits or explaining quota usage to users.",
|
|
1843
1904
|
inputSchema: {
|
|
1844
1905
|
type: "object",
|
|
1845
1906
|
properties: {
|
|
1846
|
-
email: { type: "string", description: "User email to check." }
|
|
1847
|
-
}
|
|
1848
|
-
required: ["email"]
|
|
1907
|
+
email: { type: "string", description: "Optional. User email to check. If omitted, checks usage of the current API key owner." }
|
|
1908
|
+
}
|
|
1849
1909
|
}
|
|
1850
1910
|
},
|
|
1851
1911
|
{
|
|
@@ -2036,46 +2096,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2036
2096
|
}
|
|
2037
2097
|
if (name === SEARCH_CONTEXT_TOOL) {
|
|
2038
2098
|
const query = String(args?.query);
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const remote = await manager.searchContext(query, nerveCenter.currentProjectName);
|
|
2042
|
-
if (remote && !remote.includes("No results found") && remote.trim().length > 20) {
|
|
2043
|
-
ragResults = remote;
|
|
2044
|
-
}
|
|
2045
|
-
} catch {
|
|
2046
|
-
}
|
|
2047
|
-
if (!ragResults && ragEngine) {
|
|
2048
|
-
try {
|
|
2049
|
-
const localRag = await ragEngine.search(query);
|
|
2050
|
-
if (localRag.length > 0) {
|
|
2051
|
-
ragResults = localRag.join("\n---\n");
|
|
2052
|
-
}
|
|
2053
|
-
} catch {
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
let localResults = null;
|
|
2099
|
+
logger.info(`[search_codebase] Query: "${query}"`);
|
|
2100
|
+
let localResults = "";
|
|
2057
2101
|
try {
|
|
2058
2102
|
localResults = await localSearch(query);
|
|
2103
|
+
logger.info(`[search_codebase] Local search completed: ${localResults.length} chars`);
|
|
2059
2104
|
} catch (e) {
|
|
2060
2105
|
logger.warn(`[search_codebase] Local search error: ${e}`);
|
|
2106
|
+
localResults = "";
|
|
2061
2107
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2108
|
+
let ragResults = null;
|
|
2109
|
+
const RAG_TIMEOUT_MS = 3e3;
|
|
2110
|
+
try {
|
|
2111
|
+
const ragPromise = (async () => {
|
|
2112
|
+
try {
|
|
2113
|
+
const remote = await manager.searchContext(query, nerveCenter.currentProjectName);
|
|
2114
|
+
if (remote && !remote.includes("No results found") && remote.trim().length > 20) {
|
|
2115
|
+
return remote;
|
|
2116
|
+
}
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
2119
|
+
if (ragEngine) {
|
|
2120
|
+
try {
|
|
2121
|
+
const results = await ragEngine.search(query);
|
|
2122
|
+
if (results.length > 0) return results.join("\n---\n");
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return null;
|
|
2127
|
+
})();
|
|
2128
|
+
ragResults = await Promise.race([
|
|
2129
|
+
ragPromise,
|
|
2130
|
+
new Promise((resolve) => setTimeout(() => resolve(null), RAG_TIMEOUT_MS))
|
|
2131
|
+
]);
|
|
2132
|
+
if (ragResults) {
|
|
2133
|
+
logger.info(`[search_codebase] RAG returned results (${ragResults.length} chars)`);
|
|
2134
|
+
}
|
|
2135
|
+
} catch {
|
|
2070
2136
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2137
|
+
const hasLocal = localResults && !localResults.startsWith("No matches found") && !localResults.startsWith("Could not extract");
|
|
2138
|
+
if (!hasLocal && !ragResults) {
|
|
2139
|
+
return { content: [{ type: "text", text: localResults || "No results found for this query." }] };
|
|
2073
2140
|
}
|
|
2141
|
+
const parts = [];
|
|
2142
|
+
if (hasLocal) parts.push(localResults);
|
|
2143
|
+
if (ragResults) parts.push("## Indexed Results (RAG)\n\n" + ragResults);
|
|
2074
2144
|
return { content: [{ type: "text", text: parts.join("\n\n---\n\n") }] };
|
|
2075
2145
|
}
|
|
2076
2146
|
if (name === "get_subscription_status") {
|
|
2077
|
-
const email = String(args
|
|
2078
|
-
logger.info(`[get_subscription_status] Called with email: ${email}`);
|
|
2147
|
+
const email = args?.email ? String(args.email) : void 0;
|
|
2148
|
+
logger.info(`[get_subscription_status] Called with email: ${email || "(using API key identity)"}`);
|
|
2079
2149
|
try {
|
|
2080
2150
|
const result = await nerveCenter.getSubscriptionStatus(email);
|
|
2081
2151
|
logger.info(`[get_subscription_status] Result: ${JSON.stringify(result).substring(0, 200)}`);
|
|
@@ -2086,8 +2156,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2086
2156
|
}
|
|
2087
2157
|
}
|
|
2088
2158
|
if (name === "get_usage_stats") {
|
|
2089
|
-
const email = String(args
|
|
2090
|
-
logger.info(`[get_usage_stats] Called with email: ${email}`);
|
|
2159
|
+
const email = args?.email ? String(args.email) : void 0;
|
|
2160
|
+
logger.info(`[get_usage_stats] Called with email: ${email || "(using API key identity)"}`);
|
|
2091
2161
|
try {
|
|
2092
2162
|
const result = await nerveCenter.getUsageStats(email);
|
|
2093
2163
|
logger.info(`[get_usage_stats] Result: ${JSON.stringify(result).substring(0, 200)}`);
|