@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.
@@ -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
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/server.ts'],
5
+ format: ['esm'],
6
+ dts: false,
7
+ sourcemap: true,
8
+ clean: true,
9
+ splitting: false,
10
+ shims: true,
11
+ });