@syke1/mcp-server 1.7.0 → 1.8.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/ai/project-scanner.d.ts +7 -0
- package/dist/ai/project-scanner.js +263 -0
- package/dist/index.js +69 -40
- package/dist/license/validator.d.ts +1 -1
- package/dist/license/validator.js +17 -5
- package/dist/remote/proxy.d.ts +1 -5
- package/dist/remote/proxy.js +0 -22
- package/dist/remote/types.d.ts +0 -11
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Scanner — Cortex-only tool.
|
|
3
|
+
* Scans the entire project dependency graph and generates a comprehensive
|
|
4
|
+
* onboarding document using AI (BYOK).
|
|
5
|
+
*/
|
|
6
|
+
import { DependencyGraph } from "../graph";
|
|
7
|
+
export declare function scanProject(graph: DependencyGraph): Promise<string>;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Project Scanner — Cortex-only tool.
|
|
4
|
+
* Scans the entire project dependency graph and generates a comprehensive
|
|
5
|
+
* onboarding document using AI (BYOK).
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.scanProject = scanProject;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const provider_1 = require("./provider");
|
|
45
|
+
const context_extractor_1 = require("./context-extractor");
|
|
46
|
+
// ── Config file names to look for ──
|
|
47
|
+
const CONFIG_FILE_NAMES = [
|
|
48
|
+
"package.json", "tsconfig.json", "tsconfig.base.json",
|
|
49
|
+
"Cargo.toml", "go.mod", "go.sum",
|
|
50
|
+
"pubspec.yaml", "build.gradle", "pom.xml",
|
|
51
|
+
"Gemfile", "requirements.txt", "pyproject.toml", "setup.py",
|
|
52
|
+
"Makefile", "CMakeLists.txt",
|
|
53
|
+
"docker-compose.yml", "Dockerfile",
|
|
54
|
+
".env.example",
|
|
55
|
+
];
|
|
56
|
+
const MAX_CONFIG_LINES = 200;
|
|
57
|
+
// ── Core ──
|
|
58
|
+
async function scanProject(graph) {
|
|
59
|
+
const provider = (0, provider_1.getAIProvider)();
|
|
60
|
+
if (!provider) {
|
|
61
|
+
return "scan_project requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY.";
|
|
62
|
+
}
|
|
63
|
+
const ctx = collectProjectContext(graph);
|
|
64
|
+
const systemPrompt = buildScanSystemPrompt();
|
|
65
|
+
const userPrompt = buildScanUserPrompt(ctx);
|
|
66
|
+
try {
|
|
67
|
+
return await provider.analyze(systemPrompt, userPrompt);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return `AI analysis error: ${err.message || err}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ── Context Collection ──
|
|
74
|
+
function collectProjectContext(graph) {
|
|
75
|
+
const projectRoot = graph.projectRoot;
|
|
76
|
+
// 1. Hub files — sorted by reverse dependency count
|
|
77
|
+
const hubFiles = getHubFiles(graph, 10);
|
|
78
|
+
// 2. Entry points — files with 0 reverse dependencies
|
|
79
|
+
const entryPoints = getEntryPoints(graph);
|
|
80
|
+
// 3. Circular dependencies (SCC clusters with size > 1)
|
|
81
|
+
const circularDependencies = getCircularDeps(graph);
|
|
82
|
+
// 4. Directory structure (depth-limited tree)
|
|
83
|
+
const directoryStructure = buildDirectoryTree(graph);
|
|
84
|
+
// 5. Config files
|
|
85
|
+
const configFiles = readConfigFiles(projectRoot);
|
|
86
|
+
// 6. Key file signatures (top 5 hub files)
|
|
87
|
+
const keyFileSignatures = buildKeySignatures(hubFiles.slice(0, 5), graph);
|
|
88
|
+
return {
|
|
89
|
+
projectRoot,
|
|
90
|
+
languages: graph.languages,
|
|
91
|
+
totalFiles: graph.files.size,
|
|
92
|
+
directoryStructure,
|
|
93
|
+
hubFiles,
|
|
94
|
+
entryPoints,
|
|
95
|
+
circularDependencies,
|
|
96
|
+
configFiles,
|
|
97
|
+
keyFileSignatures,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function getHubFiles(graph, topN) {
|
|
101
|
+
const counts = [];
|
|
102
|
+
for (const file of graph.files) {
|
|
103
|
+
const revDeps = graph.reverse.get(file) || [];
|
|
104
|
+
counts.push({ path: file, count: revDeps.length });
|
|
105
|
+
}
|
|
106
|
+
counts.sort((a, b) => b.count - a.count);
|
|
107
|
+
return counts.slice(0, topN).map((c) => ({
|
|
108
|
+
path: c.path,
|
|
109
|
+
relativePath: path.relative(graph.sourceDir, c.path).replace(/\\/g, "/"),
|
|
110
|
+
dependentCount: c.count,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
function getEntryPoints(graph) {
|
|
114
|
+
const entries = [];
|
|
115
|
+
for (const file of graph.files) {
|
|
116
|
+
const revDeps = graph.reverse.get(file) || [];
|
|
117
|
+
if (revDeps.length === 0) {
|
|
118
|
+
entries.push(path.relative(graph.sourceDir, file).replace(/\\/g, "/"));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return entries.slice(0, 20); // cap at 20
|
|
122
|
+
}
|
|
123
|
+
function getCircularDeps(graph) {
|
|
124
|
+
if (!graph.scc)
|
|
125
|
+
return [];
|
|
126
|
+
return graph.scc.components
|
|
127
|
+
.filter((c) => c.length > 1)
|
|
128
|
+
.map((cluster) => cluster.map((f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/")));
|
|
129
|
+
}
|
|
130
|
+
function buildDirectoryTree(graph) {
|
|
131
|
+
// Build a set of relative directory paths from file list
|
|
132
|
+
const dirs = new Map(); // dir -> file count
|
|
133
|
+
for (const file of graph.files) {
|
|
134
|
+
const rel = path.relative(graph.sourceDir, file).replace(/\\/g, "/");
|
|
135
|
+
const parts = rel.split("/");
|
|
136
|
+
// Count files per top-level and second-level directory
|
|
137
|
+
for (let depth = 1; depth <= Math.min(parts.length - 1, 3); depth++) {
|
|
138
|
+
const dirPath = parts.slice(0, depth).join("/");
|
|
139
|
+
dirs.set(dirPath, (dirs.get(dirPath) || 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Sort and format as tree
|
|
143
|
+
const sorted = [...dirs.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
144
|
+
const lines = [];
|
|
145
|
+
for (const [dirPath, count] of sorted) {
|
|
146
|
+
const depth = dirPath.split("/").length - 1;
|
|
147
|
+
const indent = " ".repeat(depth);
|
|
148
|
+
const name = dirPath.split("/").pop();
|
|
149
|
+
lines.push(`${indent}${name}/ (${count} files)`);
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n") || "(flat structure)";
|
|
152
|
+
}
|
|
153
|
+
function readConfigFiles(projectRoot) {
|
|
154
|
+
const configs = [];
|
|
155
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
156
|
+
const filePath = path.join(projectRoot, name);
|
|
157
|
+
try {
|
|
158
|
+
if (fs.existsSync(filePath)) {
|
|
159
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
160
|
+
const lines = content.split("\n");
|
|
161
|
+
const truncated = lines.length > MAX_CONFIG_LINES
|
|
162
|
+
? lines.slice(0, MAX_CONFIG_LINES).join("\n") + `\n... (${lines.length - MAX_CONFIG_LINES} more lines)`
|
|
163
|
+
: content;
|
|
164
|
+
configs.push({ path: filePath, name, content: truncated });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// skip unreadable files
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return configs;
|
|
172
|
+
}
|
|
173
|
+
function buildKeySignatures(hubFiles, graph) {
|
|
174
|
+
const parts = [];
|
|
175
|
+
for (const hub of hubFiles) {
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(hub.path, "utf-8");
|
|
178
|
+
const lang = graph.languages[0] || "typescript";
|
|
179
|
+
const summary = (0, context_extractor_1.buildSmartContext)(content, lang, 80);
|
|
180
|
+
parts.push(`### ${hub.relativePath} (${hub.dependentCount} dependents)\n${summary}`);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
parts.push(`### ${hub.relativePath} (${hub.dependentCount} dependents)\n(unable to read)`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return parts.join("\n\n");
|
|
187
|
+
}
|
|
188
|
+
// ── AI Prompts ──
|
|
189
|
+
function buildScanSystemPrompt() {
|
|
190
|
+
return `You are a senior software architect generating a comprehensive codebase onboarding document.
|
|
191
|
+
Based on the structural analysis data provided (dependency graph metrics, hub file rankings, circular dependencies, file signatures, and config files), generate a document following this exact structure:
|
|
192
|
+
|
|
193
|
+
## 1. Project Overview
|
|
194
|
+
Brief description of what this project does, its tech stack, and key technologies.
|
|
195
|
+
|
|
196
|
+
## 2. Architecture Overview
|
|
197
|
+
High-level architecture pattern (monolith, microservices, layered, etc.) and how components connect.
|
|
198
|
+
|
|
199
|
+
## 3. Repository Structure
|
|
200
|
+
Explain the directory layout and what each major directory contains.
|
|
201
|
+
|
|
202
|
+
## 4. Module / Component Map
|
|
203
|
+
Map out the key modules, their responsibilities, and how they relate to each other.
|
|
204
|
+
|
|
205
|
+
## 5. Key Concepts & Domain Model
|
|
206
|
+
Core domain concepts and abstractions that a new developer must understand.
|
|
207
|
+
|
|
208
|
+
## 6. Development Workflow
|
|
209
|
+
How to build, test, and run the project based on config files found.
|
|
210
|
+
|
|
211
|
+
## 7. Architectural Decisions
|
|
212
|
+
Notable patterns, frameworks, or design choices evident from the code structure.
|
|
213
|
+
|
|
214
|
+
## 8. Cross-Cutting Concerns
|
|
215
|
+
Shared utilities, common patterns, error handling, logging approaches found.
|
|
216
|
+
|
|
217
|
+
## 9. Danger Zones & Common Tasks
|
|
218
|
+
High-risk files (hub files with many dependents), circular dependencies to be aware of, and safe patterns for common modifications.
|
|
219
|
+
|
|
220
|
+
## 10. Quick Reference
|
|
221
|
+
Entry points, key file paths, and the most important files a new team member should read first.
|
|
222
|
+
|
|
223
|
+
Use the dependency graph data, hub file rankings, circular dependencies, entry points, and file signatures to make your analysis accurate and specific to THIS project.
|
|
224
|
+
Be concise but comprehensive. Output in Markdown. Do not include generic advice — only project-specific insights.`;
|
|
225
|
+
}
|
|
226
|
+
function buildScanUserPrompt(ctx) {
|
|
227
|
+
const sections = [];
|
|
228
|
+
sections.push(`# Project Scan Data`);
|
|
229
|
+
sections.push(`- **Root:** ${ctx.projectRoot}`);
|
|
230
|
+
sections.push(`- **Languages:** ${ctx.languages.join(", ")}`);
|
|
231
|
+
sections.push(`- **Total files in graph:** ${ctx.totalFiles}`);
|
|
232
|
+
sections.push(`\n## Directory Structure\n\`\`\`\n${ctx.directoryStructure}\n\`\`\``);
|
|
233
|
+
sections.push(`\n## Hub Files (Top ${ctx.hubFiles.length} by dependent count)`);
|
|
234
|
+
for (const h of ctx.hubFiles) {
|
|
235
|
+
sections.push(`- **${h.relativePath}** — ${h.dependentCount} dependents`);
|
|
236
|
+
}
|
|
237
|
+
sections.push(`\n## Entry Points (${ctx.entryPoints.length} files with 0 reverse deps)`);
|
|
238
|
+
for (const e of ctx.entryPoints) {
|
|
239
|
+
sections.push(`- ${e}`);
|
|
240
|
+
}
|
|
241
|
+
if (ctx.circularDependencies.length > 0) {
|
|
242
|
+
sections.push(`\n## Circular Dependencies (${ctx.circularDependencies.length} clusters)`);
|
|
243
|
+
for (let i = 0; i < ctx.circularDependencies.length; i++) {
|
|
244
|
+
sections.push(`### Cluster ${i + 1} (${ctx.circularDependencies[i].length} files)`);
|
|
245
|
+
for (const f of ctx.circularDependencies[i]) {
|
|
246
|
+
sections.push(`- ${f}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
sections.push(`\n## Circular Dependencies\nNone detected.`);
|
|
252
|
+
}
|
|
253
|
+
if (ctx.configFiles.length > 0) {
|
|
254
|
+
sections.push(`\n## Config Files`);
|
|
255
|
+
for (const cfg of ctx.configFiles) {
|
|
256
|
+
sections.push(`### ${cfg.name}\n\`\`\`\n${cfg.content}\n\`\`\``);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (ctx.keyFileSignatures) {
|
|
260
|
+
sections.push(`\n## Key File Signatures (Top Hub Files)\n${ctx.keyFileSignatures}`);
|
|
261
|
+
}
|
|
262
|
+
return sections.join("\n");
|
|
263
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,21 @@ function isPro() {
|
|
|
82
82
|
licenseStatus = { plan: "free", source: "default" };
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
|
-
return
|
|
85
|
+
return ["pro", "pro_trial", "cortex"].includes(licenseStatus.plan);
|
|
86
|
+
}
|
|
87
|
+
function isCortex() {
|
|
88
|
+
if (!(0, validator_1._verifyStatus)(licenseStatus)) {
|
|
89
|
+
licenseStatus = { plan: "free", source: "default" };
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return licenseStatus.plan === "cortex";
|
|
93
|
+
}
|
|
94
|
+
function getCortexToolError(toolName) {
|
|
95
|
+
if (licenseStatus.error)
|
|
96
|
+
return `${toolName}: ${licenseStatus.error}`;
|
|
97
|
+
if (isPro())
|
|
98
|
+
return `${toolName} requires SYKE Cortex. Upgrade at https://syke.cloud/dashboard/`;
|
|
99
|
+
return `${toolName} requires SYKE Cortex. Sign up at https://syke.cloud`;
|
|
86
100
|
}
|
|
87
101
|
function getMaxFiles() {
|
|
88
102
|
return isPro() ? undefined : FREE_MAX_FILES;
|
|
@@ -133,7 +147,7 @@ async function main() {
|
|
|
133
147
|
};
|
|
134
148
|
process.on("SIGINT", shutdown);
|
|
135
149
|
process.on("SIGTERM", shutdown);
|
|
136
|
-
const server = new index_js_1.Server({ name: "syke", version: "1.
|
|
150
|
+
const server = new index_js_1.Server({ name: "syke", version: "1.8.0" }, { capabilities: { tools: {} } });
|
|
137
151
|
// List tools
|
|
138
152
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
139
153
|
tools: [
|
|
@@ -213,7 +227,7 @@ async function main() {
|
|
|
213
227
|
},
|
|
214
228
|
{
|
|
215
229
|
name: "ai_analyze",
|
|
216
|
-
description: "[
|
|
230
|
+
description: "[CORTEX] Use AI (Gemini/OpenAI/Claude) to perform semantic analysis on a file. Reads the file's source code and its dependents to explain what might break when modified and how to safely make changes.",
|
|
217
231
|
inputSchema: {
|
|
218
232
|
type: "object",
|
|
219
233
|
properties: {
|
|
@@ -238,6 +252,16 @@ async function main() {
|
|
|
238
252
|
},
|
|
239
253
|
},
|
|
240
254
|
},
|
|
255
|
+
{
|
|
256
|
+
name: "scan_project",
|
|
257
|
+
description: "[CORTEX] Scan the entire project and generate a comprehensive onboarding document. "
|
|
258
|
+
+ "Analyzes architecture, key files, dependencies, and patterns to help new team members "
|
|
259
|
+
+ "understand the codebase quickly. Requires AI API key (BYOK).",
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
241
265
|
],
|
|
242
266
|
}));
|
|
243
267
|
// Dashboard URL footer — shown only on the first successful tool call
|
|
@@ -491,11 +515,16 @@ async function main() {
|
|
|
491
515
|
};
|
|
492
516
|
}
|
|
493
517
|
case "ai_analyze": {
|
|
494
|
-
//
|
|
518
|
+
// Cortex-only tool
|
|
519
|
+
if (!isCortex()) {
|
|
520
|
+
return {
|
|
521
|
+
content: [{ type: "text", text: getCortexToolError("ai_analyze") }],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
495
524
|
const hasAIKey = !!(0, provider_1.getAIProvider)();
|
|
496
|
-
if (!
|
|
525
|
+
if (!hasAIKey) {
|
|
497
526
|
return {
|
|
498
|
-
content: [{ type: "text", text:
|
|
527
|
+
content: [{ type: "text", text: `ai_analyze requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY in your environment.\nSee: https://syke.cloud/docs/ai-analyze` }],
|
|
499
528
|
};
|
|
500
529
|
}
|
|
501
530
|
const file = args.file;
|
|
@@ -511,37 +540,12 @@ async function main() {
|
|
|
511
540
|
],
|
|
512
541
|
};
|
|
513
542
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const impactResult = await localAnalyzeImpact(resolved, graph);
|
|
521
|
-
const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
|
|
522
|
-
let resultText = aiResult;
|
|
523
|
-
if (!isPro() && graph.files.size > FREE_MAX_FILES) {
|
|
524
|
-
resultText += `\n\n---\nFree tier: analyzing ${FREE_MAX_FILES}/${graph.files.size} files. Some dependencies may be missing. Upgrade to Pro for full analysis: https://syke.cloud/dashboard/`;
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
content: [{ type: "text", text: appendDashboardFooter(resultText) }],
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
// Pro path: use server-side AI analysis (server's Gemini key)
|
|
531
|
-
try {
|
|
532
|
-
const fs = await Promise.resolve().then(() => __importStar(require("fs")));
|
|
533
|
-
const fileContents = fs.readFileSync(resolved, "utf-8");
|
|
534
|
-
const result = await (0, proxy_1.remoteAIAnalyze)(graph, resolved, fileContents);
|
|
535
|
-
return {
|
|
536
|
-
content: [{ type: "text", text: appendDashboardFooter(result.analysis) }],
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
catch (err) {
|
|
540
|
-
return {
|
|
541
|
-
content: [{ type: "text", text: `ai_analyze failed: ${err.message || err}` }],
|
|
542
|
-
isError: true,
|
|
543
|
-
};
|
|
544
|
-
}
|
|
543
|
+
const { analyzeImpact: localAnalyzeImpact } = await Promise.resolve().then(() => __importStar(require("./tools/analyze-impact")));
|
|
544
|
+
const impactResult = await localAnalyzeImpact(resolved, graph);
|
|
545
|
+
const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
|
|
546
|
+
return {
|
|
547
|
+
content: [{ type: "text", text: appendDashboardFooter(aiResult) }],
|
|
548
|
+
};
|
|
545
549
|
}
|
|
546
550
|
case "check_warnings": {
|
|
547
551
|
// Pro-only feature (real-time monitoring)
|
|
@@ -594,6 +598,26 @@ async function main() {
|
|
|
594
598
|
content: [{ type: "text", text: appendDashboardFooter(lines.join("\n")) }],
|
|
595
599
|
};
|
|
596
600
|
}
|
|
601
|
+
case "scan_project": {
|
|
602
|
+
// Cortex-only tool
|
|
603
|
+
if (!isCortex()) {
|
|
604
|
+
return {
|
|
605
|
+
content: [{ type: "text", text: getCortexToolError("scan_project") }],
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const hasAIKey = !!(0, provider_1.getAIProvider)();
|
|
609
|
+
if (!hasAIKey) {
|
|
610
|
+
return {
|
|
611
|
+
content: [{ type: "text", text: `scan_project requires an AI API key.\n\nSet one of: GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY in your environment.\nSee: https://syke.cloud/docs/ai-analyze` }],
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const graph = (0, graph_1.getGraph)(currentProjectRoot, currentPackageName, getMaxFiles());
|
|
615
|
+
const { scanProject } = await Promise.resolve().then(() => __importStar(require("./ai/project-scanner")));
|
|
616
|
+
const result = await scanProject(graph);
|
|
617
|
+
return {
|
|
618
|
+
content: [{ type: "text", text: appendDashboardFooter(result) }],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
597
621
|
default:
|
|
598
622
|
return {
|
|
599
623
|
content: [
|
|
@@ -604,7 +628,7 @@ async function main() {
|
|
|
604
628
|
}
|
|
605
629
|
});
|
|
606
630
|
// Pre-warm the graph (skip if no project root — e.g. Smithery scan)
|
|
607
|
-
console.error(`[syke] Starting SYKE MCP Server v1.
|
|
631
|
+
console.error(`[syke] Starting SYKE MCP Server v1.8.0`);
|
|
608
632
|
console.error(`[syke] License: ${licenseStatus.plan.toUpperCase()} (${licenseStatus.source})`);
|
|
609
633
|
if (licenseStatus.expiresAt) {
|
|
610
634
|
console.error(`[syke] Expires: ${licenseStatus.expiresAt}`);
|
|
@@ -774,7 +798,7 @@ main().catch((err) => {
|
|
|
774
798
|
* See: https://smithery.ai/docs/deploy#sandbox-server
|
|
775
799
|
*/
|
|
776
800
|
function createSandboxServer() {
|
|
777
|
-
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.
|
|
801
|
+
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.8.0" }, { capabilities: { tools: {} } });
|
|
778
802
|
sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
779
803
|
tools: [
|
|
780
804
|
{
|
|
@@ -809,7 +833,7 @@ function createSandboxServer() {
|
|
|
809
833
|
},
|
|
810
834
|
{
|
|
811
835
|
name: "ai_analyze",
|
|
812
|
-
description: "
|
|
836
|
+
description: "Cortex: AI semantic analysis (Gemini/OpenAI/Claude) of a file and its dependents.",
|
|
813
837
|
inputSchema: { type: "object", properties: { file: { type: "string", description: "File to analyze" } }, required: ["file"] },
|
|
814
838
|
},
|
|
815
839
|
{
|
|
@@ -817,6 +841,11 @@ function createSandboxServer() {
|
|
|
817
841
|
description: "Pro: Real-time monitoring alerts for file changes.",
|
|
818
842
|
inputSchema: { type: "object", properties: {} },
|
|
819
843
|
},
|
|
844
|
+
{
|
|
845
|
+
name: "scan_project",
|
|
846
|
+
description: "Cortex: Scan entire project and generate onboarding document.",
|
|
847
|
+
inputSchema: { type: "object", properties: {} },
|
|
848
|
+
},
|
|
820
849
|
],
|
|
821
850
|
}));
|
|
822
851
|
sandboxServer.setRequestHandler(types_js_1.CallToolRequestSchema, async () => ({
|
|
@@ -245,6 +245,17 @@ function clearLicenseCache() {
|
|
|
245
245
|
// silently fail
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Map server plan string to client plan type.
|
|
250
|
+
* Cortex plans → "cortex", trial → "pro_trial", everything else paid → "pro".
|
|
251
|
+
*/
|
|
252
|
+
function mapPlan(serverPlan) {
|
|
253
|
+
if (serverPlan === "pro_trial")
|
|
254
|
+
return "pro_trial";
|
|
255
|
+
if (serverPlan === "cortex_monthly" || serverPlan === "cortex_annual" || serverPlan === "cortex")
|
|
256
|
+
return "cortex";
|
|
257
|
+
return "pro"; // pro_monthly, pro_annual, pro_founding, master, etc.
|
|
258
|
+
}
|
|
248
259
|
/**
|
|
249
260
|
* Main license validation — called on MCP server startup
|
|
250
261
|
*/
|
|
@@ -260,7 +271,7 @@ async function checkLicense() {
|
|
|
260
271
|
if (cache && cache.valid && (now - cache.cachedAt) < CACHE_MAX_AGE) {
|
|
261
272
|
// Cache is fresh — still start heartbeat with cached session
|
|
262
273
|
startHeartbeat(key, getDeviceFingerprint());
|
|
263
|
-
const cachedPlan = cache.plan
|
|
274
|
+
const cachedPlan = mapPlan(cache.plan || "");
|
|
264
275
|
return {
|
|
265
276
|
plan: cachedPlan,
|
|
266
277
|
email: cache.email,
|
|
@@ -285,12 +296,13 @@ async function checkLicense() {
|
|
|
285
296
|
cachedAt: now,
|
|
286
297
|
});
|
|
287
298
|
startHeartbeat(realKey, getDeviceFingerprint());
|
|
299
|
+
const foundingPlan = mapPlan(result.plan || "");
|
|
288
300
|
return {
|
|
289
|
-
plan:
|
|
301
|
+
plan: foundingPlan,
|
|
290
302
|
email: result.email,
|
|
291
303
|
expiresAt: result.expiresAt,
|
|
292
304
|
source: "online",
|
|
293
|
-
_t: _signStatus(
|
|
305
|
+
_t: _signStatus(foundingPlan, "online"),
|
|
294
306
|
};
|
|
295
307
|
}
|
|
296
308
|
if (!result.valid) {
|
|
@@ -308,7 +320,7 @@ async function checkLicense() {
|
|
|
308
320
|
});
|
|
309
321
|
// Start heartbeat
|
|
310
322
|
startHeartbeat(key, getDeviceFingerprint());
|
|
311
|
-
const onlinePlan = result.plan
|
|
323
|
+
const onlinePlan = mapPlan(result.plan || "");
|
|
312
324
|
return {
|
|
313
325
|
plan: onlinePlan,
|
|
314
326
|
email: result.email,
|
|
@@ -329,7 +341,7 @@ async function checkLicense() {
|
|
|
329
341
|
// Online validation failed — check if we have a grace-period cache
|
|
330
342
|
if (cache && cache.valid && (now - cache.cachedAt) < CACHE_GRACE_PERIOD) {
|
|
331
343
|
startHeartbeat(key, getDeviceFingerprint());
|
|
332
|
-
const gracePlan = cache.plan
|
|
344
|
+
const gracePlan = mapPlan(cache.plan || "");
|
|
333
345
|
return {
|
|
334
346
|
plan: gracePlan,
|
|
335
347
|
email: cache.email,
|
package/dist/remote/proxy.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* same output format, just executed on the server.
|
|
7
7
|
*/
|
|
8
8
|
import { DependencyGraph } from "../graph";
|
|
9
|
-
import { GraphBundle, RemoteAnalyzeImpactResponse, RemoteGetHubFilesResponse
|
|
9
|
+
import { GraphBundle, RemoteAnalyzeImpactResponse, RemoteGetHubFilesResponse } from "./types";
|
|
10
10
|
/**
|
|
11
11
|
* Serialize a DependencyGraph into a GraphBundle for transmission.
|
|
12
12
|
* Converts absolute paths to relative paths (relative to sourceDir)
|
|
@@ -23,10 +23,6 @@ export declare function remoteAnalyzeImpact(graph: DependencyGraph, resolvedFile
|
|
|
23
23
|
* Remote get_hub_files: sends graph to server for PageRank analysis.
|
|
24
24
|
*/
|
|
25
25
|
export declare function remoteGetHubFiles(graph: DependencyGraph, topN?: number): Promise<RemoteGetHubFilesResponse>;
|
|
26
|
-
/**
|
|
27
|
-
* Remote ai_analyze: sends graph + file contents to server for AI analysis.
|
|
28
|
-
*/
|
|
29
|
-
export declare function remoteAIAnalyze(graph: DependencyGraph, resolvedFilePath: string, fileContents: string): Promise<RemoteAIAnalyzeResponse>;
|
|
30
26
|
/**
|
|
31
27
|
* Remote license validation only (for refresh_graph, check_warnings).
|
|
32
28
|
* Returns true if the license is valid Pro.
|
package/dist/remote/proxy.js
CHANGED
|
@@ -43,7 +43,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
43
43
|
exports.serializeGraph = serializeGraph;
|
|
44
44
|
exports.remoteAnalyzeImpact = remoteAnalyzeImpact;
|
|
45
45
|
exports.remoteGetHubFiles = remoteGetHubFiles;
|
|
46
|
-
exports.remoteAIAnalyze = remoteAIAnalyze;
|
|
47
46
|
exports.remoteValidateLicense = remoteValidateLicense;
|
|
48
47
|
const https = __importStar(require("https"));
|
|
49
48
|
const path = __importStar(require("path"));
|
|
@@ -53,7 +52,6 @@ const BASE_URL = "https://us-central1-syke-cloud.cloudfunctions.net";
|
|
|
53
52
|
const ENDPOINTS = {
|
|
54
53
|
analyzeImpact: `${BASE_URL}/proAnalyzeImpact`,
|
|
55
54
|
getHubFiles: `${BASE_URL}/proGetHubFiles`,
|
|
56
|
-
aiAnalyze: `${BASE_URL}/proAIAnalyze`,
|
|
57
55
|
};
|
|
58
56
|
// ── Graph Serialization ──
|
|
59
57
|
/**
|
|
@@ -173,26 +171,6 @@ async function remoteGetHubFiles(graph, topN = 10) {
|
|
|
173
171
|
});
|
|
174
172
|
return result;
|
|
175
173
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Remote ai_analyze: sends graph + file contents to server for AI analysis.
|
|
178
|
-
*/
|
|
179
|
-
async function remoteAIAnalyze(graph, resolvedFilePath, fileContents) {
|
|
180
|
-
const licenseKey = getLicenseKey();
|
|
181
|
-
if (!licenseKey) {
|
|
182
|
-
throw new Error("No license key configured");
|
|
183
|
-
}
|
|
184
|
-
const graphBundle = serializeGraph(graph);
|
|
185
|
-
const targetFile = path.relative(graph.sourceDir, resolvedFilePath).replace(/\\/g, "/");
|
|
186
|
-
const result = await postJSON(ENDPOINTS.aiAnalyze, {
|
|
187
|
-
licenseKey,
|
|
188
|
-
deviceId: getDeviceId(),
|
|
189
|
-
graphBundle,
|
|
190
|
-
targetFile,
|
|
191
|
-
fileContents,
|
|
192
|
-
languages: graph.languages,
|
|
193
|
-
});
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
174
|
/**
|
|
197
175
|
* Remote license validation only (for refresh_graph, check_warnings).
|
|
198
176
|
* Returns true if the license is valid Pro.
|
package/dist/remote/types.d.ts
CHANGED
|
@@ -64,17 +64,6 @@ export interface RemoteGetHubFilesResponse {
|
|
|
64
64
|
}>;
|
|
65
65
|
totalFiles: number;
|
|
66
66
|
}
|
|
67
|
-
export interface RemoteAIAnalyzeRequest {
|
|
68
|
-
licenseKey: string;
|
|
69
|
-
deviceId: string;
|
|
70
|
-
graphBundle: GraphBundle;
|
|
71
|
-
targetFile: string;
|
|
72
|
-
fileContents: string;
|
|
73
|
-
languages: string[];
|
|
74
|
-
}
|
|
75
|
-
export interface RemoteAIAnalyzeResponse {
|
|
76
|
-
analysis: string;
|
|
77
|
-
}
|
|
78
67
|
export interface RemoteError {
|
|
79
68
|
error: string;
|
|
80
69
|
code?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"mcpName": "io.github.khalomsky/syke",
|
|
5
5
|
"description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
|
|
6
6
|
"main": "dist/index.js",
|