obsidian-native-mcp 1.1.0 → 1.2.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/.github/workflows/ci.yml +66 -38
- package/.github/workflows/release.yml +152 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +2 -2
- package/DEVELOPER.md +158 -106
- package/README.md +137 -74
- package/dist/audit/log.js +99 -0
- package/dist/audit/log.js.map +1 -0
- package/dist/cache/file-cache.js +66 -0
- package/dist/cache/file-cache.js.map +1 -0
- package/dist/cli/index.js +139 -32
- package/dist/cli/index.js.map +1 -1
- package/dist/fs/io.js +68 -0
- package/dist/fs/io.js.map +1 -0
- package/dist/fs/paths.js +41 -0
- package/dist/fs/paths.js.map +1 -0
- package/dist/fs/trash.js +20 -0
- package/dist/fs/trash.js.map +1 -0
- package/dist/fs/walk.js +73 -0
- package/dist/fs/walk.js.map +1 -0
- package/dist/handlers/args.js +97 -0
- package/dist/handlers/args.js.map +1 -0
- package/dist/handlers/registry.js +54 -0
- package/dist/handlers/registry.js.map +1 -0
- package/dist/markdown/blocks.js +95 -0
- package/dist/markdown/blocks.js.map +1 -0
- package/dist/markdown/fingerprint.js +99 -0
- package/dist/markdown/fingerprint.js.map +1 -0
- package/dist/markdown/frontmatter.js +153 -0
- package/dist/markdown/frontmatter.js.map +1 -0
- package/dist/markdown/headings.js +104 -0
- package/dist/markdown/headings.js.map +1 -0
- package/dist/markdown/links.js +93 -0
- package/dist/markdown/links.js.map +1 -0
- package/dist/markdown/outline.js +12 -0
- package/dist/markdown/outline.js.map +1 -0
- package/dist/markdown/parse-file.js +41 -0
- package/dist/markdown/parse-file.js.map +1 -0
- package/dist/markdown/parse.js +49 -0
- package/dist/markdown/parse.js.map +1 -0
- package/dist/markdown/tags.js +40 -0
- package/dist/markdown/tags.js.map +1 -0
- package/dist/mcp/framing.js +56 -0
- package/dist/mcp/framing.js.map +1 -0
- package/dist/mcp/http.js +240 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/protocol.js +16 -56
- package/dist/mcp/protocol.js.map +1 -1
- package/dist/mcp/server.js +105 -420
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.js +38 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/transport.js +4 -2
- package/dist/mcp/transport.js.map +1 -1
- package/dist/plugin/main.js +21813 -1832
- package/dist/plugin/main.js.map +1 -1
- package/dist/plugin/manifest.json +1 -1
- package/dist/plugin/settings.js +149 -41
- package/dist/plugin/settings.js.map +1 -1
- package/dist/prompts/provider.js +96 -0
- package/dist/prompts/provider.js.map +1 -0
- package/dist/tools/common.js +84 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/index.js +41 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.js +631 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write-basic.js +319 -0
- package/dist/tools/write-basic.js.map +1 -0
- package/dist/tools/write-bulk.js +331 -0
- package/dist/tools/write-bulk.js.map +1 -0
- package/dist/tools/write-patch.js +152 -0
- package/dist/tools/write-patch.js.map +1 -0
- package/dist/tools/write-structural.js +389 -0
- package/dist/tools/write-structural.js.map +1 -0
- package/dist/tools/write-surgical.js +349 -0
- package/dist/tools/write-surgical.js.map +1 -0
- package/dist/utils/log.js +2 -6
- package/dist/utils/log.js.map +1 -1
- package/dist/utils/types.js +19 -0
- package/dist/utils/types.js.map +1 -0
- package/dist/vault/permissions.js +73 -0
- package/dist/vault/permissions.js.map +1 -0
- package/dist/vault/registry.js +164 -0
- package/dist/vault/registry.js.map +1 -0
- package/eslint.config.mjs +2 -2
- package/manifest.json +1 -1
- package/package.json +18 -2
- package/scripts/start-mcp.sh +6 -0
- package/src/audit/log.ts +110 -0
- package/src/cache/file-cache.ts +92 -0
- package/src/cli/index.ts +155 -31
- package/src/fs/io.ts +69 -0
- package/src/fs/paths.ts +45 -0
- package/src/fs/trash.ts +22 -0
- package/src/fs/walk.ts +95 -0
- package/src/handlers/args.ts +129 -0
- package/src/handlers/registry.ts +100 -0
- package/src/markdown/blocks.ts +108 -0
- package/src/markdown/fingerprint.ts +112 -0
- package/src/markdown/frontmatter.ts +173 -0
- package/src/markdown/headings.ts +116 -0
- package/src/markdown/links.ts +99 -0
- package/src/markdown/outline.ts +18 -0
- package/src/markdown/parse-file.ts +51 -0
- package/src/markdown/parse.ts +53 -0
- package/src/markdown/tags.ts +42 -0
- package/src/mcp/framing.ts +59 -0
- package/src/mcp/http.ts +275 -0
- package/src/mcp/protocol.ts +41 -81
- package/src/mcp/server.ts +150 -431
- package/src/mcp/stdio.ts +41 -0
- package/src/mcp/transport.ts +12 -5
- package/src/plugin/main.ts +104 -86
- package/src/plugin/settings.ts +176 -44
- package/src/prompts/provider.ts +98 -0
- package/src/tools/common.ts +106 -0
- package/src/tools/index.ts +60 -0
- package/src/tools/read.ts +662 -0
- package/src/tools/write-basic.ts +330 -0
- package/src/tools/write-bulk.ts +355 -0
- package/src/tools/write-patch.ts +166 -0
- package/src/tools/write-structural.ts +409 -0
- package/src/tools/write-surgical.ts +378 -0
- package/src/utils/types.ts +147 -0
- package/src/vault/permissions.ts +94 -0
- package/src/vault/registry.ts +191 -0
- package/tests/fixtures/vaults/agents/AGENTS.md +16 -0
- package/tests/fixtures/vaults/code-heavy/code-heavy.md +46 -0
- package/tests/fixtures/vaults/daily-note/Daily/2026-05-21.md +21 -0
- package/tests/fixtures/vaults/dup-headings/dup-headings.md +17 -0
- package/tests/fixtures/vaults/frontmatter-stress/fm.md +17 -0
- package/tests/fixtures/vaults/large-kb/big.md +5501 -0
- package/tests/fixtures/vaults/links-zoo/Target.md +3 -0
- package/tests/fixtures/vaults/links-zoo/source.md +13 -0
- package/tests/fixtures/vaults/tiny/tiny.md +3 -0
- package/tests/helpers/sandbox.ts +107 -0
- package/tests/integration/apply-edits.test.ts +56 -0
- package/tests/integration/audit.test.ts +66 -0
- package/tests/integration/blocks.test.ts +71 -0
- package/tests/integration/file-ops.test.ts +78 -0
- package/tests/integration/links.test.ts +65 -0
- package/tests/integration/permissions.test.ts +72 -0
- package/tests/scenarios/S1-mark-tasks-done.test.ts +69 -0
- package/tests/scenarios/S10-code-fence-safety.test.ts +78 -0
- package/tests/scenarios/S11-no-fabrication.test.ts +72 -0
- package/tests/scenarios/S12-apply-patch.test.ts +92 -0
- package/tests/scenarios/S13-search-pagination.test.ts +35 -0
- package/tests/scenarios/S14-read-write-roundtrip.test.ts +76 -0
- package/tests/scenarios/S15-byte-budget.test.ts +77 -0
- package/tests/scenarios/S2-refactor-section.test.ts +87 -0
- package/tests/scenarios/S4-S5-concurrent.test.ts +85 -0
- package/tests/scenarios/S6-S7-large-file.test.ts +77 -0
- package/tests/scenarios/S8-bulk-atomic.test.ts +105 -0
- package/tests/scenarios/S9-frontmatter-nested.test.ts +63 -0
- package/tests/unit/fingerprint.test.ts +85 -0
- package/tests/unit/frontmatter.test.ts +78 -0
- package/tests/unit/headings.test.ts +66 -0
- package/tsconfig.json +5 -3
- package/dist/handlers/prompts.js +0 -100
- package/dist/handlers/prompts.js.map +0 -1
- package/dist/handlers/tools.js +0 -186
- package/dist/handlers/tools.js.map +0 -1
- package/dist/mcp/http-transport.js +0 -142
- package/dist/mcp/http-transport.js.map +0 -1
- package/dist/mcp/stdio-transport.js +0 -49
- package/dist/mcp/stdio-transport.js.map +0 -1
- package/dist/utils/fs-utils.js +0 -735
- package/dist/utils/fs-utils.js.map +0 -1
- package/dist/utils/markdown.js +0 -256
- package/dist/utils/markdown.js.map +0 -1
- package/dist/utils/search.js +0 -63
- package/dist/utils/search.js.map +0 -1
- package/dist/utils/vaults.js +0 -179
- package/dist/utils/vaults.js.map +0 -1
- package/src/handlers/prompts.ts +0 -120
- package/src/handlers/tools.ts +0 -233
- package/src/mcp/http-transport.ts +0 -159
- package/src/mcp/stdio-transport.ts +0 -54
- package/src/utils/fs-utils.ts +0 -1139
- package/src/utils/markdown.ts +0 -325
- package/src/utils/search.ts +0 -85
- package/src/utils/vaults.ts +0 -198
- package/tests/fs-utils.test.ts +0 -309
- package/tests/http-transport.test.ts +0 -111
- package/tests/protocol.test.ts +0 -36
- package/tests/server-tools.test.ts +0 -98
package/.github/workflows/ci.yml
CHANGED
|
@@ -2,74 +2,102 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
branches:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
- develop
|
|
9
|
+
- "feature/**"
|
|
10
|
+
- "renovate/**"
|
|
6
11
|
pull_request:
|
|
7
|
-
|
|
12
|
+
workflow_dispatch:
|
|
8
13
|
|
|
9
14
|
permissions:
|
|
10
15
|
contents: read
|
|
11
16
|
|
|
12
17
|
jobs:
|
|
13
|
-
|
|
14
|
-
name:
|
|
18
|
+
setup:
|
|
19
|
+
name: Setup
|
|
15
20
|
runs-on: ubuntu-latest
|
|
21
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
16
22
|
steps:
|
|
17
23
|
- uses: actions/checkout@v4
|
|
18
24
|
- uses: actions/setup-node@v4
|
|
19
25
|
with:
|
|
20
|
-
node-version:
|
|
26
|
+
node-version-file: .nvmrc
|
|
27
|
+
cache: npm
|
|
28
|
+
- run: npm ci
|
|
29
|
+
|
|
30
|
+
format:
|
|
31
|
+
name: Format
|
|
32
|
+
needs: setup
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version-file: .nvmrc
|
|
21
40
|
cache: npm
|
|
22
41
|
- run: npm ci
|
|
23
|
-
- run: npm run lint
|
|
24
42
|
- run: npm run format:check
|
|
25
|
-
- run: npm test
|
|
26
|
-
- run: npm run check
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
name:
|
|
30
|
-
needs:
|
|
44
|
+
lint:
|
|
45
|
+
name: Lint
|
|
46
|
+
needs: setup
|
|
31
47
|
runs-on: ubuntu-latest
|
|
48
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
32
49
|
steps:
|
|
33
50
|
- uses: actions/checkout@v4
|
|
34
51
|
- uses: actions/setup-node@v4
|
|
35
52
|
with:
|
|
36
|
-
node-version:
|
|
53
|
+
node-version-file: .nvmrc
|
|
37
54
|
cache: npm
|
|
38
55
|
- run: npm ci
|
|
39
|
-
- run: npm run
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
- run: npm run lint
|
|
57
|
+
|
|
58
|
+
typecheck:
|
|
59
|
+
name: Typecheck
|
|
60
|
+
needs: setup
|
|
61
|
+
runs-on: ubuntu-latest
|
|
62
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
- uses: actions/setup-node@v4
|
|
42
66
|
with:
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
node-version-file: .nvmrc
|
|
68
|
+
cache: npm
|
|
69
|
+
- run: npm ci
|
|
70
|
+
- run: npm run check
|
|
45
71
|
|
|
46
|
-
|
|
47
|
-
name:
|
|
48
|
-
|
|
49
|
-
needs: build
|
|
72
|
+
test:
|
|
73
|
+
name: Test
|
|
74
|
+
needs: setup
|
|
50
75
|
runs-on: ubuntu-latest
|
|
51
|
-
|
|
52
|
-
contents: write
|
|
53
|
-
issues: write
|
|
54
|
-
pull-requests: write
|
|
55
|
-
id-token: write
|
|
76
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
56
77
|
steps:
|
|
57
78
|
- uses: actions/checkout@v4
|
|
79
|
+
- uses: actions/setup-node@v4
|
|
58
80
|
with:
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
node-version-file: .nvmrc
|
|
82
|
+
cache: npm
|
|
83
|
+
- run: npm ci
|
|
84
|
+
- run: npm test
|
|
85
|
+
|
|
86
|
+
build:
|
|
87
|
+
name: Build
|
|
88
|
+
needs: [format, lint, typecheck, test]
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v4
|
|
61
93
|
- uses: actions/setup-node@v4
|
|
62
94
|
with:
|
|
63
|
-
node-version:
|
|
95
|
+
node-version-file: .nvmrc
|
|
64
96
|
cache: npm
|
|
65
97
|
- run: npm ci
|
|
66
|
-
- name: Install npm with OIDC support
|
|
67
|
-
run: npm install --no-save npm@11.5.1
|
|
68
98
|
- run: npm run build
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
npm --version
|
|
75
|
-
npx semantic-release
|
|
99
|
+
- run: npm run build:plugin
|
|
100
|
+
- uses: actions/upload-artifact@v4
|
|
101
|
+
with:
|
|
102
|
+
name: dist
|
|
103
|
+
path: dist/
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["CI"]
|
|
6
|
+
types:
|
|
7
|
+
- completed
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
reason:
|
|
11
|
+
description: Optional note for this manual release.
|
|
12
|
+
required: false
|
|
13
|
+
default: ""
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: release-${{ github.event.workflow_run.head_branch || github.ref }}
|
|
17
|
+
cancel-in-progress: false
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
checks: read
|
|
21
|
+
contents: write
|
|
22
|
+
issues: write
|
|
23
|
+
pull-requests: write
|
|
24
|
+
id-token: write
|
|
25
|
+
|
|
26
|
+
env:
|
|
27
|
+
RELEASE_ALLOWED_ACTORS: "usrivastava92"
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
authorize:
|
|
31
|
+
name: Authorize actor
|
|
32
|
+
if: github.event_name == 'workflow_dispatch'
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
- name: Check triggering actor against allow-list
|
|
36
|
+
env:
|
|
37
|
+
ACTOR: ${{ github.triggering_actor }}
|
|
38
|
+
ALLOWED: ${{ env.RELEASE_ALLOWED_ACTORS }}
|
|
39
|
+
run: |
|
|
40
|
+
echo "Triggered by: $ACTOR"
|
|
41
|
+
echo "Allow-list: $ALLOWED"
|
|
42
|
+
if printf '%s' ",$ALLOWED," | tr -d ' ' | grep -q ",${ACTOR},"; then
|
|
43
|
+
echo "$ACTOR is authorized to trigger releases."
|
|
44
|
+
else
|
|
45
|
+
echo "$ACTOR is NOT in RELEASE_ALLOWED_ACTORS." >&2
|
|
46
|
+
echo "Update the allow-list in .github/workflows/release.yml if needed." >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
verify-ci:
|
|
51
|
+
name: Verify CI passed
|
|
52
|
+
needs: authorize
|
|
53
|
+
if: github.event_name == 'workflow_dispatch'
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- name: Ensure target commit passed CI build
|
|
57
|
+
uses: actions/github-script@v7
|
|
58
|
+
with:
|
|
59
|
+
script: |
|
|
60
|
+
const { owner, repo } = context.repo;
|
|
61
|
+
const ref = context.sha;
|
|
62
|
+
const { data } = await github.rest.checks.listForRef({
|
|
63
|
+
owner,
|
|
64
|
+
repo,
|
|
65
|
+
ref,
|
|
66
|
+
per_page: 100,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const buildCheck = data.check_runs.find((run) => run.name === 'Build');
|
|
70
|
+
|
|
71
|
+
if (!buildCheck) {
|
|
72
|
+
core.setFailed(`No 'Build' check found for ${ref}. Run CI on this commit before releasing.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (buildCheck.status !== 'completed' || buildCheck.conclusion !== 'success') {
|
|
77
|
+
core.setFailed(
|
|
78
|
+
`'Build' check for ${ref} is ${buildCheck.status}/${buildCheck.conclusion ?? 'null'}. ` +
|
|
79
|
+
`Release requires a successful CI run first.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
release-auto:
|
|
84
|
+
name: Release npm package
|
|
85
|
+
if: |
|
|
86
|
+
github.event_name == 'workflow_run' &&
|
|
87
|
+
github.event.workflow_run.conclusion == 'success' &&
|
|
88
|
+
github.event.workflow_run.event == 'push' &&
|
|
89
|
+
github.event.workflow_run.head_branch == 'main'
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v4
|
|
93
|
+
with:
|
|
94
|
+
fetch-depth: 0
|
|
95
|
+
persist-credentials: false
|
|
96
|
+
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
|
97
|
+
|
|
98
|
+
- uses: actions/setup-node@v4
|
|
99
|
+
with:
|
|
100
|
+
node-version-file: .nvmrc
|
|
101
|
+
cache: npm
|
|
102
|
+
|
|
103
|
+
- run: npm ci
|
|
104
|
+
|
|
105
|
+
- name: Install npm with OIDC support
|
|
106
|
+
run: npm install --no-save npm@11.5.1
|
|
107
|
+
|
|
108
|
+
- run: npm run build
|
|
109
|
+
|
|
110
|
+
- name: Semantic Release
|
|
111
|
+
env:
|
|
112
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
113
|
+
run: |
|
|
114
|
+
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
115
|
+
npm --version
|
|
116
|
+
npx semantic-release
|
|
117
|
+
|
|
118
|
+
release-manual:
|
|
119
|
+
name: Release npm package (manual)
|
|
120
|
+
needs: verify-ci
|
|
121
|
+
if: github.event_name == 'workflow_dispatch'
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
steps:
|
|
124
|
+
- name: Record trigger reason
|
|
125
|
+
if: ${{ inputs.reason != '' }}
|
|
126
|
+
run: echo "Manual release reason - ${{ inputs.reason }}" >> "$GITHUB_STEP_SUMMARY"
|
|
127
|
+
|
|
128
|
+
- uses: actions/checkout@v4
|
|
129
|
+
with:
|
|
130
|
+
fetch-depth: 0
|
|
131
|
+
persist-credentials: false
|
|
132
|
+
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
|
133
|
+
|
|
134
|
+
- uses: actions/setup-node@v4
|
|
135
|
+
with:
|
|
136
|
+
node-version-file: .nvmrc
|
|
137
|
+
cache: npm
|
|
138
|
+
|
|
139
|
+
- run: npm ci
|
|
140
|
+
|
|
141
|
+
- name: Install npm with OIDC support
|
|
142
|
+
run: npm install --no-save npm@11.5.1
|
|
143
|
+
|
|
144
|
+
- run: npm run build
|
|
145
|
+
|
|
146
|
+
- name: Semantic Release
|
|
147
|
+
env:
|
|
148
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
149
|
+
run: |
|
|
150
|
+
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
151
|
+
npm --version
|
|
152
|
+
npx semantic-release
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.2.0](https://github.com/usrivastava92/obsidian-native-mcp/compare/v1.1.0...v1.2.0) (2026-05-21)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* v1.0 complete rewrite — LLM-optimized MCP server ([3215b40](https://github.com/usrivastava92/obsidian-native-mcp/commit/3215b40a12a241dcad001532efa6021d33f12465))
|
package/DEVELOPER.md
CHANGED
|
@@ -1,158 +1,210 @@
|
|
|
1
1
|
# Developer Guide
|
|
2
2
|
|
|
3
|
+
Engineering reference for **obsidian-native-mcp v1.0**. For the user-facing tool surface, see `README.md`. For design rationale and invariants, see `DESIGN_V1.md`.
|
|
4
|
+
|
|
3
5
|
## Architecture
|
|
4
6
|
|
|
5
7
|
```
|
|
6
8
|
src/
|
|
7
|
-
cli/index.ts
|
|
9
|
+
cli/index.ts # CLI entry — reads config, starts stdio transport
|
|
8
10
|
plugin/
|
|
9
|
-
main.ts
|
|
10
|
-
settings.ts
|
|
11
|
+
main.ts # Obsidian plugin entry (extends Plugin)
|
|
12
|
+
settings.ts # Settings tab: vault picker, token, per-tool toggles, audit viewer
|
|
11
13
|
mcp/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
framing.ts # Content-Length framing (encode + buffered decode)
|
|
15
|
+
protocol.ts # JSON-RPC 2.0 types + standard error codes
|
|
16
|
+
transport.ts # Transport interface
|
|
17
|
+
stdio.ts # Stdio transport (CLI)
|
|
18
|
+
http.ts # HTTP/SSE transport with bearer token, origin allowlist,
|
|
19
|
+
# body cap, max-sessions, idle TTL, heartbeat, /healthz
|
|
20
|
+
server.ts # createServer() factory — transport-agnostic request router
|
|
17
21
|
handlers/
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
registry.ts # Declarative tool registry (no boilerplate per tool)
|
|
23
|
+
args.ts # Typed argument parsers per tool kind
|
|
24
|
+
tools/
|
|
25
|
+
common.ts # Shared types: ToolContext, ToolResult envelope
|
|
26
|
+
read.ts # 14 read tools
|
|
27
|
+
write-basic.ts # file.create, file.replace, file.append, file.move, file.delete
|
|
28
|
+
write-surgical.ts # str_replace, apply_edits, lines.replace, lines.insert
|
|
29
|
+
write-structural.ts # heading.*, block.*, frontmatter.set/delete
|
|
30
|
+
write-patch.ts # apply_patch (unified diff parser)
|
|
31
|
+
write-bulk.ts # bulk.apply (atomic), regex.replace (proposal-token)
|
|
32
|
+
index.ts # Aggregate registry of all tools
|
|
33
|
+
prompts/
|
|
34
|
+
provider.ts # Vault Prompts/ scanner + Templater-style arg parser
|
|
35
|
+
markdown/
|
|
36
|
+
parse.ts # mdast parsing (gfm + frontmatter, no broken wiki-link dep)
|
|
37
|
+
parse-file.ts # Glue: text → ParsedFile (ast + headings + blocks + links + tags + frontmatter + hashes)
|
|
38
|
+
fingerprint.ts # Canonical normalization + sha256 helpers, lineOffsets
|
|
39
|
+
headings.ts # AST-aware heading extraction, section bounds, disambiguation
|
|
40
|
+
blocks.ts # ^block-id extraction with structural-type classification
|
|
41
|
+
links.ts # Typed link extraction (wiki/embed/header/block/markdown) — fence-blind
|
|
42
|
+
tags.ts # Tag extraction — fence-blind, URL-fragment-blind
|
|
43
|
+
frontmatter.ts # YAML-backed nested get/set/delete preserving formatting
|
|
44
|
+
outline.ts # Skeleton derivation from HeadingInfo
|
|
45
|
+
cache/
|
|
46
|
+
file-cache.ts # LRU<path, ParsedFile> with mtime+size invalidation, write-through
|
|
47
|
+
fs/
|
|
48
|
+
io.ts # readText, writeTextAtomic, ensureDir, uniquePath, fileExists
|
|
49
|
+
walk.ts # Async recursive walker with ignore patterns
|
|
50
|
+
trash.ts # <vault>/.obsidian/trash/ move with structure preserved
|
|
51
|
+
paths.ts # Path-traversal guard, posix/native normalization
|
|
52
|
+
vault/
|
|
53
|
+
registry.ts # VaultRegistry: env + config file + Obsidian auto-discovery
|
|
54
|
+
permissions.ts # Read-only mode, per-tool toggle, per-vault subdir rules
|
|
55
|
+
audit/
|
|
56
|
+
log.ts # JSONL audit log + sha256 args hashing + 5MB rotation
|
|
20
57
|
utils/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
vaults.ts VaultRegistry — config from env, file, Obsidian auto-discovery
|
|
58
|
+
types.ts # Canonical types: ParsedFile, HeadingInfo, BlockInfo, ExtractedLink, ToolError, etc.
|
|
59
|
+
log.ts # Structured stderr logger (key=value)
|
|
24
60
|
```
|
|
25
61
|
|
|
26
|
-
###
|
|
62
|
+
### Layering rules
|
|
27
63
|
|
|
28
|
-
|
|
64
|
+
1. `markdown/` and `fs/` are leaf modules — no dependencies on `tools/`, `handlers/`, or `mcp/`.
|
|
65
|
+
2. `cache/` depends only on `markdown/` and `fs/`.
|
|
66
|
+
3. `tools/` depends on `markdown/`, `fs/`, `cache/`, `audit/`, `vault/` — never on `mcp/` or `handlers/`.
|
|
67
|
+
4. `handlers/` orchestrates `tools/` and wraps results into MCP responses.
|
|
68
|
+
5. `mcp/` knows nothing about tools — just transports and JSON-RPC.
|
|
29
69
|
|
|
30
|
-
|
|
70
|
+
### Key design decisions
|
|
31
71
|
|
|
32
|
-
**
|
|
72
|
+
- **Surgical-first edits.** `str_replace` / `apply_patch` / `apply_edits` are the workhorses. Whole-file `file.replace` is the documented escape hatch.
|
|
73
|
+
- **Hash-based concurrency.** Every read returns content hashes (file, range, section, block, frontmatter, line). Every write that targets an existing range requires the matching `expected_*_hash`. Stale precondition → structured `STALE_PRECONDITION` error with current hashes, never silent clobbering.
|
|
74
|
+
- **Server is source of truth for hashes.** Clients echo opaque strings the server gave them. We never trust client-computed hashes.
|
|
75
|
+
- **AST-aware everywhere.** mdast classifies code-fenced text as `code` nodes, HTML-commented blocks as `html` nodes — so heading/block/tag/link extractors get fence-safety _for free_.
|
|
76
|
+
- **Real YAML for frontmatter.** We use the `yaml` package for round-trip preserving nested get/set/delete. No more hand-rolled line matching.
|
|
77
|
+
- **Two transports, shared core.** `createServer()` returns `{handleRequest, toolDefinitions}`. Stdio and HTTP/SSE differ only in I/O layer.
|
|
78
|
+
- **Process-atomic bulk writes.** `bulk.apply` with `atomic: true` snapshots originals in memory, applies all ops, writes, and restores on any failure.
|
|
33
79
|
|
|
34
|
-
|
|
80
|
+
## Development workflow
|
|
35
81
|
|
|
36
|
-
|
|
82
|
+
```bash
|
|
83
|
+
npm install
|
|
84
|
+
npm run dev # tsx watch on the CLI
|
|
85
|
+
npm run check # tsc --noEmit
|
|
86
|
+
npm run lint # eslint src/
|
|
87
|
+
npm run format # prettier --write src/
|
|
88
|
+
npm test # node:test runner across tests/**/*.test.ts
|
|
89
|
+
npm run test:coverage # c8 with --check-coverage thresholds
|
|
90
|
+
npm run build # tsc → dist/
|
|
91
|
+
npm run build:plugin # esbuild plugin bundle for Obsidian
|
|
92
|
+
```
|
|
37
93
|
|
|
38
|
-
|
|
94
|
+
### Testing the CLI manually
|
|
39
95
|
|
|
40
96
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
97
|
+
OBSIDIAN_VAULT_PATHS=/path/to/vault node dist/cli/index.js
|
|
98
|
+
```
|
|
43
99
|
|
|
44
|
-
|
|
45
|
-
npm run dev
|
|
100
|
+
Then pipe in Content-Length framed JSON-RPC requests on stdin. The `tests/helpers/sandbox.ts` helper does this programmatically for tests.
|
|
46
101
|
|
|
47
|
-
|
|
48
|
-
npm run check
|
|
102
|
+
### Testing the Plugin
|
|
49
103
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
104
|
+
1. `npm run build:plugin`
|
|
105
|
+
2. Copy `dist/plugin/` to `<your-vault>/.obsidian/plugins/obsidian-native-mcp/`
|
|
106
|
+
3. Reload Obsidian, enable in Community Plugins, open settings tab.
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
npm run build
|
|
108
|
+
## Adding a new tool
|
|
56
109
|
|
|
57
|
-
|
|
58
|
-
|
|
110
|
+
1. Decide its layer: read-only, surgical write, structural write, whole-file write, or batch.
|
|
111
|
+
2. Add the implementation to the right file under `src/tools/` (`read.ts`, `write-surgical.ts`, etc.).
|
|
112
|
+
3. Export it via `src/tools/index.ts` so the aggregate registry picks it up.
|
|
113
|
+
4. Add an argument parser in `src/handlers/args.ts` if its shape doesn't fit an existing one.
|
|
114
|
+
5. Write tests:
|
|
115
|
+
- **Unit** for any pure helper logic (`tests/unit/...`).
|
|
116
|
+
- **Integration** for the tool itself (`tests/integration/<tool>.test.ts`).
|
|
117
|
+
- **Scenario** if it changes a multi-step LLM flow (`tests/scenarios/...`).
|
|
118
|
+
6. Update the relevant table in `README.md` and the schema list in `src/tools/index.ts`.
|
|
59
119
|
|
|
60
|
-
|
|
61
|
-
npm run start
|
|
120
|
+
### Tool contract checklist
|
|
62
121
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
122
|
+
- Reads must return hashes the corresponding write expects (`contentHash`, `rangeHash`, `sectionHash`, `blockHash`, `frontmatterHash`, `lineHash`).
|
|
123
|
+
- Writes that target existing ranges must accept `expected_*_hash` and return `STALE_PRECONDITION` on mismatch.
|
|
124
|
+
- Mutating tools must support `dry_run: true` returning the _would-be_ new hashes without touching disk.
|
|
125
|
+
- Errors must be `{ok: false, error: {code, message, ...details}}` — never throw across the JSON-RPC boundary.
|
|
126
|
+
- Every mutating tool appends one audit-log line via `AuditLog`.
|
|
66
127
|
|
|
67
|
-
|
|
128
|
+
## Test strategy
|
|
68
129
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
];
|
|
76
|
-
let input = "";
|
|
77
|
-
for(const m of msgs){ const s = JSON.stringify(m); input += "Content-Length: "+s.length+"\r\n\r\n"+s; }
|
|
78
|
-
const {spawn} = await import("child_process");
|
|
79
|
-
const child = spawn("node", ["dist/cli/index.js"], {stdio:["pipe","pipe","pipe"]});
|
|
80
|
-
let buf = "";
|
|
81
|
-
child.stdout.on("data", c => buf += c.toString());
|
|
82
|
-
child.on("close", () => {
|
|
83
|
-
let remaining = buf;
|
|
84
|
-
while(true){
|
|
85
|
-
const m = remaining.match(/^Content-Length: (\d+)\r\n\r\n/);
|
|
86
|
-
if(!m) break;
|
|
87
|
-
const len = parseInt(m[1]), start = m[0].length;
|
|
88
|
-
if(remaining.length < start + len) break;
|
|
89
|
-
console.log(JSON.parse(remaining.slice(start, start + len)));
|
|
90
|
-
remaining = remaining.slice(start + len);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
child.stdin.write(input);
|
|
94
|
-
child.stdin.end();
|
|
95
|
-
setTimeout(() => process.exit(0), 2000);
|
|
96
|
-
'
|
|
97
|
-
```
|
|
130
|
+
| Layer | Location | What |
|
|
131
|
+
| ----------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
132
|
+
| Unit | `tests/unit/` | Pure-function correctness for `markdown/`, `fingerprint`, `frontmatter`, `headings` |
|
|
133
|
+
| Integration | `tests/integration/` | One file per tool family — permissions, blocks, apply_edits, audit, links, file-ops |
|
|
134
|
+
| Scenario | `tests/scenarios/` | Multi-tool LLM flows: S1 mark-tasks-done, S2 refactor section, S4/S5 concurrency, S6/S7 large file, S8 bulk rollback, S9 nested frontmatter, S10 code-fence safety, S11 no-fabrication, S12 apply_patch validation, S13 search pagination, S14 read-write roundtrip, S15 byte-budget benchmark |
|
|
135
|
+
| Property | (inline in unit) | Round-trip and hash-stability invariants |
|
|
98
136
|
|
|
99
|
-
|
|
137
|
+
The headline benchmark (`S15`) asserts that a surgical workflow uses ≤ 30 % of the byte budget of the equivalent whole-file rewrite — that's the v1.0 thesis in test form.
|
|
100
138
|
|
|
101
|
-
|
|
102
|
-
2. Copy `dist/plugin/` to `<your-vault>/.obsidian/plugins/obsidian-native-mcp/`
|
|
103
|
-
3. Reload Obsidian, enable the plugin in Community Plugins
|
|
104
|
-
4. Open plugin settings to see discovered vaults
|
|
139
|
+
### Fixtures
|
|
105
140
|
|
|
106
|
-
|
|
141
|
+
Under `tests/fixtures/vaults/`:
|
|
107
142
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
- `tiny/` — smoke
|
|
144
|
+
- `agents/` — realistic AGENTS.md shape
|
|
145
|
+
- `daily-note/` — task lists, headings, frontmatter
|
|
146
|
+
- `code-heavy/` — fenced code with heading- and tag-looking content inside (the single biggest historical bug source)
|
|
147
|
+
- `dup-headings/` — multiple `## Tasks` plus `### Tasks` for disambiguation tests
|
|
148
|
+
- `frontmatter-stress/` — block scalars, nested maps, similar key names
|
|
149
|
+
- `links-zoo/` — wiki, embed, header-ref, block-ref, aliased, markdown
|
|
150
|
+
- `large-kb/` — 5,500-line generated file for outline + search perf
|
|
111
151
|
|
|
112
|
-
|
|
152
|
+
Add more fixtures whenever you find a new edge class — the test suite rewards breadth.
|
|
113
153
|
|
|
114
|
-
|
|
115
|
-
# 1. Update version in package.json and manifest.json
|
|
116
|
-
# 2. Build
|
|
117
|
-
npm run build
|
|
118
|
-
npm run build:plugin
|
|
154
|
+
## Release process
|
|
119
155
|
|
|
120
|
-
|
|
156
|
+
```bash
|
|
157
|
+
# Bump version in package.json + manifest.json (the prepare hook + sync-version.cjs keep them aligned)
|
|
158
|
+
npm run check && npm run lint && npm test && npm run build && npm run build:plugin
|
|
121
159
|
npm publish
|
|
160
|
+
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
|
|
161
|
+
# Plugin: submit dist/plugin/ to https://github.com/obsidianmd/obsidian-releases
|
|
162
|
+
```
|
|
122
163
|
|
|
123
|
-
|
|
124
|
-
gh release create v0.2.0 --title "v0.2.0" --generate-notes
|
|
164
|
+
CI uses semantic-release on the `main` branch.
|
|
125
165
|
|
|
126
|
-
|
|
127
|
-
# https://github.com/obsidianmd/obsidian-releases
|
|
128
|
-
```
|
|
166
|
+
## Protocol reference
|
|
129
167
|
|
|
130
|
-
|
|
168
|
+
The server speaks standard MCP over stdio (CLI) or HTTP/SSE (plugin).
|
|
131
169
|
|
|
132
|
-
|
|
170
|
+
### Stdio framing
|
|
133
171
|
|
|
134
|
-
|
|
172
|
+
The CLI uses the official `@modelcontextprotocol/sdk` (`StdioServerTransport`), which as of v1.29.0 uses **newline-delimited JSON**:
|
|
135
173
|
|
|
136
174
|
```
|
|
137
|
-
Client → Server:
|
|
138
|
-
Server → Client:
|
|
175
|
+
Client → Server: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}\n
|
|
176
|
+
Server → Client: {"jsonrpc":"2.0","id":1,"result":{...}}\n
|
|
139
177
|
```
|
|
140
178
|
|
|
179
|
+
Note: The MCP spec originally described Content-Length framing (LSP-style). The official SDK switched to newline-delimited JSON in recent versions. We follow the SDK — not our own framing implementation. `src/mcp/framing.ts` and `src/mcp/stdio.ts` are retained for the HTTP/SSE plugin transport but are not used by the CLI.
|
|
180
|
+
|
|
141
181
|
### HTTP/SSE
|
|
142
182
|
|
|
143
183
|
```
|
|
144
|
-
Client → Server: GET /sse → SSE stream
|
|
145
|
-
Client → Server: POST /message?session_id=<id> → JSON-RPC request
|
|
146
|
-
Server → Client: SSE event "message" with JSON-RPC response
|
|
184
|
+
Client → Server: GET /sse?token=<bearer> → SSE stream starts; first event is "endpoint" with the POST URL
|
|
185
|
+
Client → Server: POST /message?session_id=<id> → JSON-RPC request (body capped at 5 MB)
|
|
186
|
+
Server → Client: SSE event "message" with the JSON-RPC response
|
|
187
|
+
Server → Client: Heartbeat events keep the connection alive; idle sessions are GC'd
|
|
147
188
|
```
|
|
148
189
|
|
|
149
|
-
|
|
190
|
+
`/healthz` returns `{ok: true, sessions: N, version: "X.Y.Z"}`.
|
|
191
|
+
|
|
192
|
+
### Supported methods
|
|
193
|
+
|
|
194
|
+
| Method | Purpose |
|
|
195
|
+
| --------------------------- | ------------------------------------------------------ |
|
|
196
|
+
| `initialize` | Protocol handshake |
|
|
197
|
+
| `tools/list` | List available tools (filtered by current permissions) |
|
|
198
|
+
| `tools/call` | Execute a tool |
|
|
199
|
+
| `prompts/list` | List available prompts (from vault Prompts/ folders) |
|
|
200
|
+
| `prompts/get` | Get prompt content with arguments substituted |
|
|
201
|
+
| `notifications/initialized` | Client-ready notification |
|
|
202
|
+
|
|
203
|
+
## Logs and auditing
|
|
204
|
+
|
|
205
|
+
- **Stderr logs** are key=value structured (`component=http session=abc msg="…"`), safe to ship to journald, OpsAgent, etc.
|
|
206
|
+
- **Audit log** is JSONL at `<vault>/.obsidian/plugins/obsidian-native-mcp/audit.log`, one line per mutating call. Use it for forensic analysis and regression replays.
|
|
207
|
+
|
|
208
|
+
## See also
|
|
150
209
|
|
|
151
|
-
|
|
152
|
-
| --------------------------- | ------------------------- |
|
|
153
|
-
| `initialize` | Protocol handshake |
|
|
154
|
-
| `tools/list` | List available tools |
|
|
155
|
-
| `tools/call` | Execute a tool |
|
|
156
|
-
| `prompts/list` | List available prompts |
|
|
157
|
-
| `prompts/get` | Get prompt content |
|
|
158
|
-
| `notifications/initialized` | Client-ready notification |
|
|
210
|
+
- `DESIGN_V1.md` — design rationale, invariants, tool surface, and roadmap.
|