obsidian-mcp-local 1.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/README.md +160 -0
- package/dist/index.js +13 -0
- package/dist/server.js +18 -0
- package/dist/tools/append-to-note.js +15 -0
- package/dist/tools/create-note.js +16 -0
- package/dist/tools/find-by-tag.js +10 -0
- package/dist/tools/get-note.js +10 -0
- package/dist/tools/search-notes.js +10 -0
- package/dist/vault/fs-utils.js +32 -0
- package/dist/vault/notes.js +112 -0
- package/dist/vault/path-utils.js +30 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Obsidian MCP Local
|
|
2
|
+
|
|
3
|
+
MCP local em **Node.js + TypeScript** para expor seu **vault do Obsidian** ao **VS Code + GitHub Copilot**.
|
|
4
|
+
|
|
5
|
+
Ele foi pensado para uso local via **stdio**, com foco em ler e escrever notas Markdown dentro do seu vault.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
npm install -g obsidian-mcp-local
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### Tools disponíveis
|
|
14
|
+
|
|
15
|
+
- `search_notes(query)`
|
|
16
|
+
- busca por texto no path, frontmatter e conteúdo das notas
|
|
17
|
+
- retorna resultados ranqueados com pequeno excerpt
|
|
18
|
+
|
|
19
|
+
- `get_note(path)`
|
|
20
|
+
- abre uma nota do vault
|
|
21
|
+
- retorna `path`, `frontmatter` e `content`
|
|
22
|
+
|
|
23
|
+
- `create_note(path, content, overwrite?)`
|
|
24
|
+
- cria uma nota nova
|
|
25
|
+
- opcionalmente sobrescreve uma nota existente
|
|
26
|
+
|
|
27
|
+
- `append_to_note(path, content)`
|
|
28
|
+
- adiciona conteúdo no final de uma nota existente
|
|
29
|
+
|
|
30
|
+
- `find_by_tag(tag)`
|
|
31
|
+
- encontra notas por tag
|
|
32
|
+
- suporta `tags` no frontmatter e tags inline no conteúdo
|
|
33
|
+
|
|
34
|
+
## Regras implementadas
|
|
35
|
+
|
|
36
|
+
- só acessa arquivos **dentro do vault configurado**
|
|
37
|
+
- ignora diretórios como:
|
|
38
|
+
- `.obsidian`
|
|
39
|
+
- `.git`
|
|
40
|
+
- `node_modules`
|
|
41
|
+
- trabalha apenas com arquivos `.md`
|
|
42
|
+
- normaliza paths para evitar acesso fora do diretório base
|
|
43
|
+
|
|
44
|
+
## Estrutura do projeto
|
|
45
|
+
|
|
46
|
+
```txt
|
|
47
|
+
obsidian-mcp-local/
|
|
48
|
+
package.json
|
|
49
|
+
tsconfig.json
|
|
50
|
+
README.md
|
|
51
|
+
.vscode/
|
|
52
|
+
mcp.example.json
|
|
53
|
+
src/
|
|
54
|
+
index.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Pré-requisitos
|
|
58
|
+
|
|
59
|
+
- Node.js 20+
|
|
60
|
+
- npm
|
|
61
|
+
- VS Code com GitHub Copilot
|
|
62
|
+
- um vault do Obsidian local
|
|
63
|
+
|
|
64
|
+
## Instalação
|
|
65
|
+
|
|
66
|
+
No diretório do projeto:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install
|
|
70
|
+
npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Para desenvolvimento:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Para rodar a versão compilada:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm start
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Como usar no VS Code
|
|
86
|
+
|
|
87
|
+
### 1. Compile o projeto
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm install
|
|
91
|
+
npm run build
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Ajuste o arquivo MCP do VS Code
|
|
95
|
+
|
|
96
|
+
Copie o conteúdo de `.vscode/mcp.example.json` para o seu `.vscode/mcp.json` no workspace onde você vai usar o Copilot.
|
|
97
|
+
|
|
98
|
+
Exemplo:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"servers": {
|
|
103
|
+
"obsidian-local-vault": {
|
|
104
|
+
"type": "stdio",
|
|
105
|
+
"command": "node",
|
|
106
|
+
"args": ["C:/caminho/para/obsidian-mcp-local/dist/index.js"],
|
|
107
|
+
"env": {
|
|
108
|
+
"OBSIDIAN_VAULT_PATH": "D:/Obsidian/Vault"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Atualize os caminhos
|
|
116
|
+
|
|
117
|
+
Substitua:
|
|
118
|
+
|
|
119
|
+
- `C:/caminho/para/obsidian-mcp-local/dist/index.js`
|
|
120
|
+
- `D:/Obsidian/Vault`
|
|
121
|
+
|
|
122
|
+
pelos caminhos reais da sua máquina.
|
|
123
|
+
|
|
124
|
+
### 4. Reinicie/recarrregue o VS Code
|
|
125
|
+
|
|
126
|
+
Depois disso, o Copilot deve descobrir o servidor MCP.
|
|
127
|
+
|
|
128
|
+
## Exemplos de uso no Copilot Chat
|
|
129
|
+
|
|
130
|
+
- “Procure no meu vault notas sobre .NET”
|
|
131
|
+
- “Abra a nota `knowledge/backend/dotnet.md`”
|
|
132
|
+
- “Crie uma nota em `inbox/ideias-mcp.md` com um resumo do que discutimos”
|
|
133
|
+
- “Adicione no final da nota `daily/2026-04-06.md` o texto `- testar MCP local`”
|
|
134
|
+
- “Encontre notas com a tag `#arquitetura`”
|
|
135
|
+
|
|
136
|
+
## Possíveis melhorias futuras
|
|
137
|
+
|
|
138
|
+
- `append_under_heading`
|
|
139
|
+
- parsing de `[[wikilinks]]`
|
|
140
|
+
- `get_backlinks(note)`
|
|
141
|
+
- índice em SQLite para busca rápida
|
|
142
|
+
- whitelist de pastas para escrita (`inbox/`, `daily/`, `scratch/`)
|
|
143
|
+
- bloqueio configurável de escrita em determinadas pastas
|
|
144
|
+
|
|
145
|
+
## Observações importantes
|
|
146
|
+
|
|
147
|
+
- Este projeto **não depende do Obsidian aberto**.
|
|
148
|
+
- Ele opera diretamente sobre os arquivos do vault.
|
|
149
|
+
- Se você habilitar escrita tanto no Obsidian quanto no VS Code, o controle de concorrência fica por sua conta.
|
|
150
|
+
- O projeto hoje assume que o vault é uma pasta Markdown local.
|
|
151
|
+
|
|
152
|
+
## Arquivo principal
|
|
153
|
+
|
|
154
|
+
A implementação está em:
|
|
155
|
+
|
|
156
|
+
- `src/index.ts`
|
|
157
|
+
|
|
158
|
+
## Licença
|
|
159
|
+
|
|
160
|
+
Uso pessoal / base inicial para customização.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
const transport = new StdioServerTransport();
|
|
8
|
+
await server.connect(transport);
|
|
9
|
+
}
|
|
10
|
+
main().catch((error) => {
|
|
11
|
+
console.error("[obsidian-mcp-local] Fatal error:", error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as searchNotes from "./tools/search-notes.js";
|
|
3
|
+
import * as getNote from "./tools/get-note.js";
|
|
4
|
+
import * as createNote from "./tools/create-note.js";
|
|
5
|
+
import * as appendToNote from "./tools/append-to-note.js";
|
|
6
|
+
import * as findByTag from "./tools/find-by-tag.js";
|
|
7
|
+
export function createServer() {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "obsidian-local-vault",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
});
|
|
12
|
+
searchNotes.register(server);
|
|
13
|
+
getNote.register(server);
|
|
14
|
+
createNote.register(server);
|
|
15
|
+
appendToNote.register(server);
|
|
16
|
+
findByTag.register(server);
|
|
17
|
+
return server;
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { appendToNote } from "../vault/notes.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.registerTool("append_to_note", {
|
|
5
|
+
inputSchema: {
|
|
6
|
+
path: z.string().min(1),
|
|
7
|
+
content: z.string().min(1),
|
|
8
|
+
},
|
|
9
|
+
}, async ({ path, content }) => {
|
|
10
|
+
const result = await appendToNote(path, content);
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createNote } from "../vault/notes.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.registerTool("create_note", {
|
|
5
|
+
inputSchema: {
|
|
6
|
+
path: z.string().min(1),
|
|
7
|
+
content: z.string(),
|
|
8
|
+
overwrite: z.boolean().optional(),
|
|
9
|
+
},
|
|
10
|
+
}, async ({ path, content, overwrite }) => {
|
|
11
|
+
const result = await createNote(path, content, overwrite ?? false);
|
|
12
|
+
return {
|
|
13
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { findByTag } from "../vault/notes.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.registerTool("find_by_tag", { inputSchema: { tag: z.string().min(1) } }, async ({ tag }) => {
|
|
5
|
+
const results = await findByTag(tag);
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readNote } from "../vault/notes.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.registerTool("get_note", { inputSchema: { path: z.string().min(1) } }, async ({ path }) => {
|
|
5
|
+
const note = await readNote(path);
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchNotes } from "../vault/notes.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.registerTool("search_notes", { inputSchema: { query: z.string().min(1) } }, async ({ query }) => {
|
|
5
|
+
const results = await searchNotes(query);
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
8
|
+
};
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isMarkdownFile } from "./path-utils.js";
|
|
4
|
+
export async function pathExists(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(filePath);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function walkMarkdownFiles(dir) {
|
|
14
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
15
|
+
const files = [];
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const fullPath = path.join(dir, entry.name);
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
if (entry.name === ".obsidian" ||
|
|
20
|
+
entry.name === ".git" ||
|
|
21
|
+
entry.name === "node_modules") {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
files.push(...(await walkMarkdownFiles(fullPath)));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.isFile() && isMarkdownFile(entry.name)) {
|
|
28
|
+
files.push(fullPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { VAULT_ROOT, ensureMdExtension, resolveVaultPath, toRelativeVaultPath, } from "./path-utils.js";
|
|
5
|
+
import { pathExists, walkMarkdownFiles } from "./fs-utils.js";
|
|
6
|
+
export async function readNote(relativePath) {
|
|
7
|
+
const finalPath = ensureMdExtension(relativePath);
|
|
8
|
+
const fullPath = resolveVaultPath(finalPath);
|
|
9
|
+
if (!(await pathExists(fullPath))) {
|
|
10
|
+
throw new Error(`Note not found: ${finalPath}`);
|
|
11
|
+
}
|
|
12
|
+
const raw = await fs.readFile(fullPath, "utf-8");
|
|
13
|
+
const parsed = matter(raw);
|
|
14
|
+
return {
|
|
15
|
+
path: finalPath,
|
|
16
|
+
frontmatter: parsed.data,
|
|
17
|
+
content: parsed.content,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export async function searchNotes(query) {
|
|
21
|
+
const q = query.trim().toLowerCase();
|
|
22
|
+
if (!q)
|
|
23
|
+
return [];
|
|
24
|
+
const files = await walkMarkdownFiles(VAULT_ROOT);
|
|
25
|
+
const matches = [];
|
|
26
|
+
for (const fullPath of files) {
|
|
27
|
+
const raw = await fs.readFile(fullPath, "utf-8");
|
|
28
|
+
const parsed = matter(raw);
|
|
29
|
+
const relativePath = toRelativeVaultPath(fullPath);
|
|
30
|
+
const haystack = `${relativePath}\n${JSON.stringify(parsed.data)}\n${parsed.content}`.toLowerCase();
|
|
31
|
+
const idx = haystack.indexOf(q);
|
|
32
|
+
if (idx >= 0) {
|
|
33
|
+
const contentLower = parsed.content.toLowerCase();
|
|
34
|
+
const contentIdx = contentLower.indexOf(q);
|
|
35
|
+
const excerpt = contentIdx >= 0
|
|
36
|
+
? parsed.content
|
|
37
|
+
.slice(Math.max(0, contentIdx - 120), Math.min(parsed.content.length, contentIdx + 220))
|
|
38
|
+
.trim()
|
|
39
|
+
: "";
|
|
40
|
+
let score = 1;
|
|
41
|
+
if (relativePath.toLowerCase().includes(q))
|
|
42
|
+
score += 4;
|
|
43
|
+
if (JSON.stringify(parsed.data).toLowerCase().includes(q))
|
|
44
|
+
score += 2;
|
|
45
|
+
if (contentIdx >= 0)
|
|
46
|
+
score += 1;
|
|
47
|
+
matches.push({ path: relativePath, score, excerpt });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return matches
|
|
51
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
52
|
+
.slice(0, 20);
|
|
53
|
+
}
|
|
54
|
+
export async function findByTag(tag) {
|
|
55
|
+
const normalizedTag = tag.replace(/^#/, "").trim().toLowerCase();
|
|
56
|
+
if (!normalizedTag)
|
|
57
|
+
return [];
|
|
58
|
+
const files = await walkMarkdownFiles(VAULT_ROOT);
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const fullPath of files) {
|
|
61
|
+
const raw = await fs.readFile(fullPath, "utf-8");
|
|
62
|
+
const parsed = matter(raw);
|
|
63
|
+
const relativePath = toRelativeVaultPath(fullPath);
|
|
64
|
+
const matchedIn = [];
|
|
65
|
+
const fmTags = parsed.data?.tags;
|
|
66
|
+
if (Array.isArray(fmTags)) {
|
|
67
|
+
const found = fmTags.some((t) => String(t).replace(/^#/, "").toLowerCase() === normalizedTag);
|
|
68
|
+
if (found)
|
|
69
|
+
matchedIn.push("frontmatter.tags");
|
|
70
|
+
}
|
|
71
|
+
const inlineTagRegex = /(^|\s)#([a-zA-Z0-9/_-]+)/g;
|
|
72
|
+
for (const match of parsed.content.matchAll(inlineTagRegex)) {
|
|
73
|
+
const foundTag = match[2]?.toLowerCase();
|
|
74
|
+
if (foundTag === normalizedTag) {
|
|
75
|
+
matchedIn.push("content");
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (matchedIn.length > 0) {
|
|
80
|
+
results.push({ path: relativePath, matchedIn });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return results.sort((a, b) => a.path.localeCompare(b.path));
|
|
84
|
+
}
|
|
85
|
+
export async function createNote(relativePath, content, overwrite = false) {
|
|
86
|
+
const finalPath = ensureMdExtension(relativePath);
|
|
87
|
+
const fullPath = resolveVaultPath(finalPath);
|
|
88
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
89
|
+
const exists = await pathExists(fullPath);
|
|
90
|
+
if (exists && !overwrite) {
|
|
91
|
+
throw new Error(`Note already exists: ${finalPath}`);
|
|
92
|
+
}
|
|
93
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
94
|
+
return {
|
|
95
|
+
path: finalPath,
|
|
96
|
+
created: !exists,
|
|
97
|
+
overwritten: exists && overwrite,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export async function appendToNote(relativePath, content) {
|
|
101
|
+
const finalPath = ensureMdExtension(relativePath);
|
|
102
|
+
const fullPath = resolveVaultPath(finalPath);
|
|
103
|
+
if (!(await pathExists(fullPath))) {
|
|
104
|
+
throw new Error(`Note not found: ${finalPath}`);
|
|
105
|
+
}
|
|
106
|
+
const prefix = content.startsWith("\n") ? "" : "\n";
|
|
107
|
+
await fs.appendFile(fullPath, `${prefix}${content}`, "utf-8");
|
|
108
|
+
return {
|
|
109
|
+
path: finalPath,
|
|
110
|
+
appended: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const vaultPath = process.env.OBSIDIAN_VAULT_PATH;
|
|
3
|
+
if (!vaultPath) {
|
|
4
|
+
throw new Error("Environment variable OBSIDIAN_VAULT_PATH is required.");
|
|
5
|
+
}
|
|
6
|
+
export const VAULT_ROOT = path.resolve(vaultPath);
|
|
7
|
+
export function normalizeSlashes(input) {
|
|
8
|
+
return input.replace(/\\/g, "/");
|
|
9
|
+
}
|
|
10
|
+
export function isMarkdownFile(filePath) {
|
|
11
|
+
return filePath.toLowerCase().endsWith(".md");
|
|
12
|
+
}
|
|
13
|
+
export function ensureMdExtension(filePath) {
|
|
14
|
+
return isMarkdownFile(filePath) ? filePath : `${filePath}.md`;
|
|
15
|
+
}
|
|
16
|
+
export function resolveVaultPath(relativePath) {
|
|
17
|
+
const safeRelativePath = normalizeSlashes(relativePath).replace(/^\/+/, "");
|
|
18
|
+
const fullPath = path.resolve(VAULT_ROOT, safeRelativePath);
|
|
19
|
+
const rootWithSep = VAULT_ROOT.endsWith(path.sep)
|
|
20
|
+
? VAULT_ROOT
|
|
21
|
+
: `${VAULT_ROOT}${path.sep}`;
|
|
22
|
+
const isInsideVault = fullPath === VAULT_ROOT || fullPath.startsWith(rootWithSep);
|
|
23
|
+
if (!isInsideVault) {
|
|
24
|
+
throw new Error("Access outside the vault is not allowed.");
|
|
25
|
+
}
|
|
26
|
+
return fullPath;
|
|
27
|
+
}
|
|
28
|
+
export function toRelativeVaultPath(fullPath) {
|
|
29
|
+
return normalizeSlashes(path.relative(VAULT_ROOT, fullPath));
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "obsidian-mcp-local",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"author": "Henrique Carvalho de Souza",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"description": "MCP local para um vault do Obsidian, pensado para uso com VS Code + GitHub Copilot.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"bin": {
|
|
11
|
+
"obsidian-mcp-local": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"obsidian",
|
|
26
|
+
"copilot",
|
|
27
|
+
"ai",
|
|
28
|
+
"knowledge-base"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.11.4",
|
|
32
|
+
"dotenv": "^17.4.1",
|
|
33
|
+
"gray-matter": "^4.0.3",
|
|
34
|
+
"zod": "^3.24.3"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.19.17",
|
|
38
|
+
"tsx": "^4.19.3",
|
|
39
|
+
"typescript": "^5.8.2"
|
|
40
|
+
}
|
|
41
|
+
}
|