@noesis-brain/mcp-server 2.0.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/api/NoesisClient.d.ts +501 -0
- package/dist/api/NoesisClient.d.ts.map +1 -0
- package/dist/api/NoesisClient.js +654 -0
- package/dist/api/NoesisClient.js.map +1 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +148 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/database/PostgresAdapter.d.ts +385 -0
- package/dist/database/PostgresAdapter.d.ts.map +1 -0
- package/dist/database/PostgresAdapter.js +1043 -0
- package/dist/database/PostgresAdapter.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/services/embedding.d.ts +38 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +126 -0
- package/dist/services/embedding.js.map +1 -0
- package/dist/tools/SyncStateManager.d.ts +65 -0
- package/dist/tools/SyncStateManager.d.ts.map +1 -0
- package/dist/tools/SyncStateManager.js +217 -0
- package/dist/tools/SyncStateManager.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3345 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/navis.d.ts +11 -0
- package/dist/tools/navis.d.ts.map +1 -0
- package/dist/tools/navis.js +231 -0
- package/dist/tools/navis.js.map +1 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/suggestPath.d.ts +15 -0
- package/dist/utils/suggestPath.d.ts.map +1 -0
- package/dist/utils/suggestPath.js +52 -0
- package/dist/utils/suggestPath.js.map +1 -0
- package/package.json +71 -0
- package/scripts/noesis-sync.mjs +469 -0
- package/skill-templates/noesis-refine-note.md +92 -0
- package/skill-templates/noesis-sync.md +110 -0
- package/templates/claude-md-block.md +22 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Note types for md-manager MCP server
|
|
3
|
+
*/
|
|
4
|
+
export interface Note {
|
|
5
|
+
id: number;
|
|
6
|
+
title: string;
|
|
7
|
+
file_path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
keywords?: string;
|
|
11
|
+
points?: number;
|
|
12
|
+
is_favorite?: number;
|
|
13
|
+
modified_at?: string;
|
|
14
|
+
created_at?: string;
|
|
15
|
+
hash?: string;
|
|
16
|
+
importance_score?: number;
|
|
17
|
+
quality_score?: number;
|
|
18
|
+
relations?: Relation[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Document relation (Phase 6.2)
|
|
22
|
+
* Bidirectional: forward types have inverse counterparts
|
|
23
|
+
*/
|
|
24
|
+
export interface Relation {
|
|
25
|
+
type: RelationType;
|
|
26
|
+
target_id: number;
|
|
27
|
+
context?: string;
|
|
28
|
+
}
|
|
29
|
+
export type RelationType = 'references' | 'referenced_by' | 'implements' | 'implemented_by' | 'extends' | 'extended_by' | 'supersedes' | 'superseded_by';
|
|
30
|
+
export interface SearchResult {
|
|
31
|
+
id: number;
|
|
32
|
+
title: string;
|
|
33
|
+
file_path: string;
|
|
34
|
+
content: string;
|
|
35
|
+
excerpt: string;
|
|
36
|
+
relevance: number;
|
|
37
|
+
modified_at?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SearchOptions {
|
|
40
|
+
limit?: number;
|
|
41
|
+
root?: string;
|
|
42
|
+
dateRange?: {
|
|
43
|
+
start: string;
|
|
44
|
+
end: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface SyncResult {
|
|
48
|
+
synced: number;
|
|
49
|
+
skipped: number;
|
|
50
|
+
errors: string[];
|
|
51
|
+
details: Array<{
|
|
52
|
+
file: string;
|
|
53
|
+
action: 'created' | 'updated' | 'skipped' | 'error';
|
|
54
|
+
reason?: string;
|
|
55
|
+
aiMetadata?: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Bidirectional sync result - tracks both push and pull operations
|
|
60
|
+
*/
|
|
61
|
+
export interface BidirectionalSyncResult {
|
|
62
|
+
pushed: {
|
|
63
|
+
created: number;
|
|
64
|
+
updated: number;
|
|
65
|
+
};
|
|
66
|
+
pulled: {
|
|
67
|
+
created: number;
|
|
68
|
+
updated: number;
|
|
69
|
+
};
|
|
70
|
+
skipped: number;
|
|
71
|
+
conflicts: Array<{
|
|
72
|
+
path: string;
|
|
73
|
+
localModified: string;
|
|
74
|
+
cloudModified: string;
|
|
75
|
+
}>;
|
|
76
|
+
errors: string[];
|
|
77
|
+
details: Array<{
|
|
78
|
+
file: string;
|
|
79
|
+
action: 'pushed_create' | 'pushed_update' | 'pulled_create' | 'pulled_update' | 'skipped' | 'conflict' | 'error';
|
|
80
|
+
reason?: string;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
export interface SyncStatus {
|
|
84
|
+
pending: number;
|
|
85
|
+
lastSync: string | null;
|
|
86
|
+
roots: Array<{
|
|
87
|
+
name: string;
|
|
88
|
+
path: string;
|
|
89
|
+
fileCount: number;
|
|
90
|
+
lastScanned: string | null;
|
|
91
|
+
}>;
|
|
92
|
+
}
|
|
93
|
+
export interface LocalFile {
|
|
94
|
+
path: string;
|
|
95
|
+
relativePath: string;
|
|
96
|
+
content: string;
|
|
97
|
+
hash: string;
|
|
98
|
+
mtime: Date;
|
|
99
|
+
size: number;
|
|
100
|
+
rootId: number;
|
|
101
|
+
rootName: string;
|
|
102
|
+
project?: string;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform default-path suggestion helper.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: a near-identical copy lives in src/frontend/src/utils/osPlatform.ts.
|
|
5
|
+
* The two files are in separate npm packages with no shared util layer; if you
|
|
6
|
+
* change one, change the other.
|
|
7
|
+
*
|
|
8
|
+
* Used by the MCP `add_root` tool when the user supplies a path for one OS and
|
|
9
|
+
* we want to pre-fill the other side. Only emits a non-empty suggestion when
|
|
10
|
+
* the input is recognizably home-relative — otherwise returns '' so the caller
|
|
11
|
+
* leaves the other slot empty rather than guessing wrong.
|
|
12
|
+
*/
|
|
13
|
+
export type OsKey = 'win32' | 'darwin' | 'linux';
|
|
14
|
+
export declare function suggestOtherOsPath(inputPath: string, sourceOs: OsKey): string;
|
|
15
|
+
//# sourceMappingURL=suggestPath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestPath.d.ts","sourceRoot":"","sources":["../../src/utils/suggestPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,GAAG,MAAM,CAkC7E"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform default-path suggestion helper.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: a near-identical copy lives in src/frontend/src/utils/osPlatform.ts.
|
|
5
|
+
* The two files are in separate npm packages with no shared util layer; if you
|
|
6
|
+
* change one, change the other.
|
|
7
|
+
*
|
|
8
|
+
* Used by the MCP `add_root` tool when the user supplies a path for one OS and
|
|
9
|
+
* we want to pre-fill the other side. Only emits a non-empty suggestion when
|
|
10
|
+
* the input is recognizably home-relative — otherwise returns '' so the caller
|
|
11
|
+
* leaves the other slot empty rather than guessing wrong.
|
|
12
|
+
*/
|
|
13
|
+
export function suggestOtherOsPath(inputPath, sourceOs) {
|
|
14
|
+
if (typeof inputPath !== 'string')
|
|
15
|
+
return '';
|
|
16
|
+
const trimmed = inputPath.trim().replace(/[/\\]+$/, '');
|
|
17
|
+
if (!trimmed)
|
|
18
|
+
return '';
|
|
19
|
+
if (sourceOs === 'win32') {
|
|
20
|
+
// Only suggest if under %USERPROFILE% (the C:\Users\<x>\... convention).
|
|
21
|
+
const m = trimmed.match(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/](.+)$/i);
|
|
22
|
+
if (!m)
|
|
23
|
+
return '';
|
|
24
|
+
const subpath = m[1].replace(/\\/g, '/');
|
|
25
|
+
return '~/' + subpath;
|
|
26
|
+
}
|
|
27
|
+
if (sourceOs === 'darwin' || sourceOs === 'linux') {
|
|
28
|
+
let subpath = null;
|
|
29
|
+
if (trimmed.startsWith('~/')) {
|
|
30
|
+
subpath = trimmed.slice(2);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const mac = trimmed.match(/^\/Users\/[^/]+\/(.+)$/);
|
|
34
|
+
if (mac) {
|
|
35
|
+
subpath = mac[1];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const lin = trimmed.match(/^\/home\/[^/]+\/(.+)$/);
|
|
39
|
+
if (lin)
|
|
40
|
+
subpath = lin[1];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!subpath)
|
|
44
|
+
return '';
|
|
45
|
+
// %USERNAME% is a template token — caller must expand on first use.
|
|
46
|
+
// Leaving it literal signals "this is a guess, edit it" instead of
|
|
47
|
+
// silently inserting the wrong username.
|
|
48
|
+
return 'C:\\Users\\%USERNAME%\\' + subpath.replace(/\//g, '\\');
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=suggestPath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestPath.js","sourceRoot":"","sources":["../../src/utils/suggestPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,QAAe;IACnE,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,yEAAyE;QACzE,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACvE,IAAI,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACzC,OAAO,IAAI,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClD,IAAI,OAAO,GAAkB,IAAI,CAAC;QAClC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;YACpD,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;gBACnD,IAAI,GAAG;oBAAE,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QACxB,oEAAoE;QACpE,mEAAmE;QACnE,yCAAyC;QACzC,OAAO,yBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noesis-brain/mcp-server",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Official MCP server for Noesis — bring your personal knowledge base into Claude Code. Search, sync, and manage your notes via 19 AI-powered tools.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"noesis-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"scripts",
|
|
13
|
+
"skill-templates",
|
|
14
|
+
"templates",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"dev": "tsx watch src/index.ts"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"claude",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"noesis",
|
|
29
|
+
"markdown",
|
|
30
|
+
"notes",
|
|
31
|
+
"knowledge-base",
|
|
32
|
+
"personal-knowledge-management",
|
|
33
|
+
"pkm",
|
|
34
|
+
"second-brain",
|
|
35
|
+
"ai",
|
|
36
|
+
"semantic-search"
|
|
37
|
+
],
|
|
38
|
+
"author": "Noesis",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"homepage": "https://noesisbrain.com",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/noesis-brain/mcp-server.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/noesis-brain/mcp-server/issues"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@google/genai": "^1.0.0",
|
|
56
|
+
"@google/generative-ai": "^0.21.0",
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
58
|
+
"dotenv": "^16.3.0",
|
|
59
|
+
"js-yaml": "^4.1.1",
|
|
60
|
+
"node-diff3": "^3.2.0",
|
|
61
|
+
"pg": "^8.11.0",
|
|
62
|
+
"zod": "^3.23.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/js-yaml": "^4.0.9",
|
|
66
|
+
"@types/node": "^22.0.0",
|
|
67
|
+
"@types/pg": "^8.10.0",
|
|
68
|
+
"tsx": "^4.19.0",
|
|
69
|
+
"typescript": "^5.6.0"
|
|
70
|
+
}
|
|
71
|
+
}
|