@mycontxt/mcp 0.1.1
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/.turbo/turbo-build.log +15 -0
- package/dist/server.js +988 -0
- package/dist/server.js.map +1 -0
- package/package.json +24 -0
- package/src/server.ts +433 -0
- package/src/tools/auto-capture.ts +377 -0
- package/src/tools/index.ts +330 -0
- package/src/tools/suggest-context.ts +76 -0
- package/src/utils/project.ts +19 -0
- package/src/utils/usage-gate.ts +149 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggest Context Tool - Smart context retrieval
|
|
3
|
+
* This is the killer feature of MemoCore
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SQLiteDatabase } from '@mycontxt/adapters/sqlite';
|
|
7
|
+
import { rankEntries, fitToBudget, buildContext } from '@mycontxt/core';
|
|
8
|
+
import type { SuggestOptions } from '@mycontxt/core';
|
|
9
|
+
import { getDbPath } from '../utils/project.js';
|
|
10
|
+
|
|
11
|
+
interface SuggestContextArgs {
|
|
12
|
+
taskDescription?: string;
|
|
13
|
+
activeFiles?: string[];
|
|
14
|
+
maxTokens?: number;
|
|
15
|
+
minRelevance?: number;
|
|
16
|
+
projectPath?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function suggestContext(args: SuggestContextArgs): Promise<string> {
|
|
20
|
+
const projectPath = args.projectPath || process.cwd();
|
|
21
|
+
const dbPath = getDbPath(projectPath);
|
|
22
|
+
|
|
23
|
+
const db = new SQLiteDatabase(dbPath);
|
|
24
|
+
await db.initialize();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Get project
|
|
28
|
+
const project = await db.getProjectByPath(projectPath);
|
|
29
|
+
if (!project) {
|
|
30
|
+
return 'No MemoCore project found. Run `memocore init` to initialize.';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get active branch
|
|
34
|
+
const activeBranch = await db.getActiveBranch(project.id);
|
|
35
|
+
|
|
36
|
+
// Get all non-archived entries
|
|
37
|
+
const entries = await db.listEntries({
|
|
38
|
+
projectId: project.id,
|
|
39
|
+
branch: activeBranch,
|
|
40
|
+
isArchived: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (entries.length === 0) {
|
|
44
|
+
return 'No memory entries found. Add some decisions, patterns, or context to get started.';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Build suggest options
|
|
48
|
+
const options: SuggestOptions = {
|
|
49
|
+
projectId: project.id,
|
|
50
|
+
taskDescription: args.taskDescription,
|
|
51
|
+
activeFiles: args.activeFiles,
|
|
52
|
+
maxTokens: args.maxTokens || 4000,
|
|
53
|
+
minRelevance: args.minRelevance || 0.3,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Rank entries by relevance
|
|
57
|
+
const ranked = rankEntries(entries, options);
|
|
58
|
+
|
|
59
|
+
if (ranked.length === 0) {
|
|
60
|
+
return 'No relevant entries found for the given task.';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fit within token budget
|
|
64
|
+
const fitted = fitToBudget(ranked, options.maxTokens);
|
|
65
|
+
|
|
66
|
+
// Build formatted context
|
|
67
|
+
const context = buildContext(fitted, {
|
|
68
|
+
includeReasons: true,
|
|
69
|
+
includeStats: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return context;
|
|
73
|
+
} finally {
|
|
74
|
+
await db.close();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project utilities for MCP server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the .contxt directory path
|
|
9
|
+
*/
|
|
10
|
+
export function getContxtDir(cwd: string): string {
|
|
11
|
+
return join(cwd, '.contxt');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the database file path
|
|
16
|
+
*/
|
|
17
|
+
export function getDbPath(cwd: string): string {
|
|
18
|
+
return join(getContxtDir(cwd), 'local.db');
|
|
19
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage gate utilities for MCP server
|
|
3
|
+
* MCP MUST NEVER throw errors - always degrade gracefully
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { UsageGate, type UsageCounts } from '@mycontxt/core/engine/usage';
|
|
10
|
+
import { resolveUserPlan } from '@mycontxt/core/engine/plan-resolver';
|
|
11
|
+
import { SupabaseDatabase } from '@mycontxt/adapters/supabase';
|
|
12
|
+
|
|
13
|
+
const AUTH_FILE = join(homedir(), '.contxt', 'auth.json');
|
|
14
|
+
|
|
15
|
+
interface AuthData {
|
|
16
|
+
accessToken: string;
|
|
17
|
+
userId: string;
|
|
18
|
+
email: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get current authenticated user ID
|
|
23
|
+
*/
|
|
24
|
+
function getCurrentUserId(): string | null {
|
|
25
|
+
if (!existsSync(AUTH_FILE)) return null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const content = readFileSync(AUTH_FILE, 'utf-8');
|
|
29
|
+
const auth: AuthData = JSON.parse(content);
|
|
30
|
+
return auth.userId;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get authenticated Supabase database instance
|
|
38
|
+
*/
|
|
39
|
+
function getRemoteDb(): SupabaseDatabase | null {
|
|
40
|
+
if (!existsSync(AUTH_FILE)) return null;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(AUTH_FILE, 'utf-8');
|
|
44
|
+
const auth: AuthData = JSON.parse(content);
|
|
45
|
+
|
|
46
|
+
return new SupabaseDatabase({
|
|
47
|
+
url: process.env.SUPABASE_URL || '',
|
|
48
|
+
anonKey: process.env.SUPABASE_ANON_KEY || '',
|
|
49
|
+
accessToken: auth.accessToken,
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get usage counts for gate enforcement
|
|
58
|
+
*/
|
|
59
|
+
async function getUsageCounts(
|
|
60
|
+
localDb: any,
|
|
61
|
+
userId: string | null,
|
|
62
|
+
projectId?: string
|
|
63
|
+
): Promise<UsageCounts> {
|
|
64
|
+
const projectCountQuery = 'SELECT COUNT(*) as count FROM projects';
|
|
65
|
+
const projectRow = localDb.db.prepare(projectCountQuery).get() as any;
|
|
66
|
+
const totalProjects = projectRow.count;
|
|
67
|
+
|
|
68
|
+
const totalEntriesQuery = 'SELECT COUNT(*) as count FROM memory_entries WHERE is_archived = 0';
|
|
69
|
+
const totalEntriesRow = localDb.db.prepare(totalEntriesQuery).get() as any;
|
|
70
|
+
const totalEntries = totalEntriesRow.count;
|
|
71
|
+
|
|
72
|
+
let entriesInProject = 0;
|
|
73
|
+
if (projectId) {
|
|
74
|
+
const projectEntriesQuery = 'SELECT COUNT(*) as count FROM memory_entries WHERE project_id = ? AND is_archived = 0';
|
|
75
|
+
const projectEntriesRow = localDb.db.prepare(projectEntriesQuery).get(projectId) as any;
|
|
76
|
+
entriesInProject = projectEntriesRow.count;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
totalProjects,
|
|
81
|
+
totalEntries,
|
|
82
|
+
entriesInProject,
|
|
83
|
+
totalSeats: 1,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if entry creation is allowed
|
|
89
|
+
* Returns { allowed: true } or { allowed: false, message: string }
|
|
90
|
+
*/
|
|
91
|
+
export async function checkEntryAllowed(
|
|
92
|
+
localDb: any,
|
|
93
|
+
projectId?: string
|
|
94
|
+
): Promise<{ allowed: boolean; message?: string }> {
|
|
95
|
+
try {
|
|
96
|
+
const userId = getCurrentUserId();
|
|
97
|
+
const remoteDb = getRemoteDb();
|
|
98
|
+
|
|
99
|
+
const planId = await resolveUserPlan(localDb, remoteDb, userId);
|
|
100
|
+
const gate = new UsageGate(planId, () => getUsageCounts(localDb, userId, projectId));
|
|
101
|
+
|
|
102
|
+
const result = await gate.checkEntryCreate();
|
|
103
|
+
|
|
104
|
+
if (!result.allowed) {
|
|
105
|
+
return {
|
|
106
|
+
allowed: false,
|
|
107
|
+
message: `\n\n---\n💡 ${result.reason}\n${result.upgradeHint}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { allowed: true };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// On error, allow the operation (fail open)
|
|
114
|
+
console.error('Usage gate error:', error);
|
|
115
|
+
return { allowed: true };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if a feature is enabled
|
|
121
|
+
* Returns { allowed: true } or { allowed: false, message: string }
|
|
122
|
+
*/
|
|
123
|
+
export async function checkFeatureAllowed(
|
|
124
|
+
localDb: any,
|
|
125
|
+
feature: 'semanticSearchEnabled' | 'smartSuggestEnabled' | 'branchingEnabled'
|
|
126
|
+
): Promise<{ allowed: boolean; message?: string }> {
|
|
127
|
+
try {
|
|
128
|
+
const userId = getCurrentUserId();
|
|
129
|
+
const remoteDb = getRemoteDb();
|
|
130
|
+
|
|
131
|
+
const planId = await resolveUserPlan(localDb, remoteDb, userId);
|
|
132
|
+
const gate = new UsageGate(planId, () => getUsageCounts(localDb, userId));
|
|
133
|
+
|
|
134
|
+
const result = gate.checkFeature(feature);
|
|
135
|
+
|
|
136
|
+
if (!result.allowed) {
|
|
137
|
+
return {
|
|
138
|
+
allowed: false,
|
|
139
|
+
message: `\n\n---\n💡 ${result.reason}\n${result.upgradeHint}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { allowed: true };
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// On error, allow the operation (fail open)
|
|
146
|
+
console.error('Feature gate error:', error);
|
|
147
|
+
return { allowed: true };
|
|
148
|
+
}
|
|
149
|
+
}
|
package/tsconfig.json
ADDED