obsidian_fetch 0.1.10 → 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.
- checksums.yaml +4 -4
- data/README.md +9 -10
- data/exe/obsidian_fetch +1 -70
- data/lib/exe.rb +59 -0
- data/lib/obsidian_fetch/version.rb +1 -1
- data/lib/obsidian_fetch.rb +55 -14
- metadata +9 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02563af73296a2af37e1ea12b6943dad193c5dc77a24ee5d77462d2d846366a2
|
4
|
+
data.tar.gz: fabf0d51658773dc3b708e737116914bde4b55f9e43dda066911c6576f5b38bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83967477cbd13c96797d9254b755af6f5436a70877344b1353dc48c406e8cd9a0bd8d7a061480a125da53e4f66c7126290295645ade26f47a64ba64d9be15c61
|
7
|
+
data.tar.gz: f2baff1697d33d8fe8c97fef41b9efeac56b0386bcfc623745e1658a1d7ad1fb9e4c1e0a363c171814f156dad0fc3ca61de9f8c469ca3eef6344ba7622a8e29d
|
data/README.md
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
# ObsidianFetch
|
2
2
|
|
3
|
-
MCP servers
|
3
|
+
MCP servers focused on fetching and presenting information from Obsidian vaults.
|
4
4
|
|
5
5
|
The existing MCP server has the following drawbacks:
|
6
|
-
-
|
7
|
-
- When reading a note labeled "
|
8
|
-
- Some tools
|
6
|
+
- It supports many commands, which can cause slow prompt loading when computational resources are limited.
|
7
|
+
- When reading a note labeled "Sample Note", it is necessary to search for its path first before loading it, but the LLM may not always follow this procedure.
|
8
|
+
- Some tools include unnecessary options, leading the LLM to sometimes fail to invoke them correctly.
|
9
9
|
|
10
10
|
These issues become particularly noticeable when running an LLM on a local GPU.
|
11
|
-
To address this, we developed a new MCP server that simply retrieves and loads
|
11
|
+
To address this, we developed a new MCP server that simply retrieves and loads lists of notes.
|
12
12
|
|
13
|
-
|
14
|
-
- When the LLM
|
15
|
-
-
|
16
|
-
-
|
17
|
-
- Support for aliases.
|
13
|
+
The new server also provides the following additional features:
|
14
|
+
- When the LLM attempts to retrieve link information by searching with brackets like `[[link name]]`, the server automatically removes any characters that cannot be used in links.
|
15
|
+
- In addition to loading the note contents, it also displays backlinks—notes that link to the currently opened note.
|
16
|
+
- This allows the LLM to load and understand the connections between related notes via backlinks.
|
18
17
|
|
19
18
|
## Installation
|
20
19
|
|
data/exe/obsidian_fetch
CHANGED
@@ -1,73 +1,4 @@
|
|
1
1
|
# !/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require '
|
5
|
-
|
6
|
-
|
7
|
-
name 'obsidian-fetch'
|
8
|
-
|
9
|
-
version '0.1.0'
|
10
|
-
|
11
|
-
vault_pathes = ARGV
|
12
|
-
|
13
|
-
vault = ObsidianFetch::Vault.new(vault_pathes)
|
14
|
-
STDERR.puts "Found #{vault.notes.size} notes"
|
15
|
-
STDERR.puts "Found #{vault.links_by_file_name.size} links and #{vault.links_by_file_path.size} files linked by notes"
|
16
|
-
|
17
|
-
tool 'read' do
|
18
|
-
description <<~EOS
|
19
|
-
Read a note from Obsidian vault.
|
20
|
-
If I find multiple notes with the same name, I will show you all of them.
|
21
|
-
EOS
|
22
|
-
argument :name, String, required: true, description: "Note name to read"
|
23
|
-
call do |args|
|
24
|
-
name = args[:name]
|
25
|
-
# 名前が文字列でない場合
|
26
|
-
next 'Name must be a string' unless name.is_a?(String)
|
27
|
-
vault.tool_read(name)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
tool 'read_multiple' do
|
32
|
-
description <<~EOS
|
33
|
-
Read a notes from Obsidian vault.
|
34
|
-
If I find multiple notes with the same name, I will show you all of them.
|
35
|
-
EOS
|
36
|
-
argument :names, Array, items: String, required: true, description: "Note names to read"
|
37
|
-
call do |args|
|
38
|
-
names = args[:names]
|
39
|
-
# 名前が文字列の配列でない場合
|
40
|
-
next 'Name must be an array of strings' unless names.is_a?(Array) && names.all? { |name| name.is_a?(String) }
|
41
|
-
names.map do |name|
|
42
|
-
vault.tool_read(name)
|
43
|
-
end.join("\n\n---\n\n")
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
tool 'list' do
|
48
|
-
description <<~EOS
|
49
|
-
Search for files with matching names partially.
|
50
|
-
EOS
|
51
|
-
argument :name, String, required: true, description: "Note name to search"
|
52
|
-
call do |args|
|
53
|
-
name = args[:name]
|
54
|
-
# 名前が文字列でない場合
|
55
|
-
next 'Name must be a string' unless name.is_a?(String)
|
56
|
-
vault.tool_list(name)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
tool 'list_multiple' do
|
61
|
-
description <<~EOS
|
62
|
-
Search for files with matching names partially.
|
63
|
-
EOS
|
64
|
-
argument :names, Array, items: String, required: true, description: "Note names to search"
|
65
|
-
call do |args|
|
66
|
-
names = args[:names]
|
67
|
-
# 名前が文字列の配列でない場合
|
68
|
-
next 'Name must be an array of strings' unless names.is_a?(Array) && names.all? { |name| name.is_a?(String) }
|
69
|
-
names.map do |name|
|
70
|
-
vault.tool_list(name)
|
71
|
-
end.join("\n\n---\n\n")
|
72
|
-
end
|
73
|
-
end
|
4
|
+
require 'exe'
|
data/lib/exe.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'mcp'
|
2
|
+
require 'obsidian_fetch'
|
3
|
+
|
4
|
+
# Obsidian Vaultの初期化
|
5
|
+
vault_paths = ARGV
|
6
|
+
$vault = ObsidianFetch::Vault.new(vault_paths)
|
7
|
+
STDERR.puts "Found #{$vault.notes.size} notes"
|
8
|
+
STDERR.puts "Found #{$vault.links_by_file_name.size} links and #{$vault.links_by_file_path.size} files linked by notes"
|
9
|
+
|
10
|
+
# readツールの定義
|
11
|
+
class ReadTool < MCP::Tool
|
12
|
+
description "Read a note from Obsidian vault. If multiple notes with the same name are found, all will be shown."
|
13
|
+
input_schema(
|
14
|
+
properties: {
|
15
|
+
name: { type: "string", description: "Note name to read" }
|
16
|
+
},
|
17
|
+
required: ["name"]
|
18
|
+
)
|
19
|
+
|
20
|
+
def self.call(name:)
|
21
|
+
# 名前が文字列でない場合
|
22
|
+
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }]) unless name.is_a?(String)
|
23
|
+
|
24
|
+
# Vaultからノートを読み取る
|
25
|
+
result = $vault.tool_read(name)
|
26
|
+
MCP::Tool::Response.new([{ type: "text", text: result }])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# listツールの定義
|
31
|
+
class ListTool < MCP::Tool
|
32
|
+
description "Search for files with matching names partially."
|
33
|
+
input_schema(
|
34
|
+
properties: {
|
35
|
+
name: { type: "string", description: "Note name to search" }
|
36
|
+
},
|
37
|
+
required: ["name"]
|
38
|
+
)
|
39
|
+
|
40
|
+
def self.call(name:)
|
41
|
+
# 名前が文字列でない場合
|
42
|
+
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }]) unless name.is_a?(String)
|
43
|
+
|
44
|
+
# Vaultからノートを検索
|
45
|
+
result = $vault.tool_list(name)
|
46
|
+
MCP::Tool::Response.new([{ type: "text", text: result }])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# MCPサーバーの初期化
|
51
|
+
server = MCP::Server.new(
|
52
|
+
name: "obsidian-fetch",
|
53
|
+
version: "0.1.0",
|
54
|
+
tools: [ReadTool, ListTool],
|
55
|
+
)
|
56
|
+
|
57
|
+
# Stdioトランスポートを使用してサーバーを起動
|
58
|
+
transport = MCP::Server::Transports::StdioTransport.new(server)
|
59
|
+
transport.open
|
data/lib/obsidian_fetch.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'date'
|
5
|
-
require 'mcp'
|
6
5
|
|
7
6
|
require_relative "obsidian_fetch/version"
|
8
7
|
|
@@ -82,6 +81,7 @@ module ObsidianFetch
|
|
82
81
|
# もしリンクが設定されていれば、linksに追加する
|
83
82
|
# [[link]]と[[link|displayname]]の場合
|
84
83
|
# linkに.mdが付いている場合、付いていない場合両方を考慮する
|
84
|
+
# TODO : linkにディレクトリ名が着くことがあるので、それを除去する
|
85
85
|
content.scan(/\[\[(.*?)(?:\|.*)?\]\]/) do |match|
|
86
86
|
link_name = match[0]
|
87
87
|
link_name = link_name.sub(/\.md$/, '') # .mdを削除
|
@@ -133,19 +133,60 @@ module ObsidianFetch
|
|
133
133
|
def tool_read name
|
134
134
|
name = Vault.normalize_note_name(name)
|
135
135
|
file_pathes = @notes[name]
|
136
|
+
|
137
|
+
# 名前のノートが見つからないが、nameがパスっぽい場合は、パスを修正したうえでもう一度試す
|
138
|
+
preface = ""
|
139
|
+
if file_pathes.nil? && name.include?('/')
|
140
|
+
fixed_name = File.basename(name, '.md')
|
141
|
+
file_pathes = @notes[fixed_name]
|
142
|
+
if file_pathes.nil?
|
143
|
+
# もしも名前で見つからなければ、リンクにも存在しないか確認する
|
144
|
+
link_pathes = @links_by_file_name[fixed_name]
|
145
|
+
if link_pathes.nil?
|
146
|
+
return note_not_found(name)
|
147
|
+
else
|
148
|
+
# リンク先のノートが見つかった場合は、prefaceを追加する
|
149
|
+
preface = <<~EOS
|
150
|
+
Presumably a path was specified. The process was automatically renamed and processed.
|
151
|
+
EOS
|
152
|
+
return list_links(fixed_name, preface)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
# ノート名が見つかった場合は、prefaceを追加する
|
156
|
+
preface = <<~EOS
|
157
|
+
Presumably a path was specified. The process was automatically renamed and processed.
|
158
|
+
EOS
|
159
|
+
return open_file(fixed_name, file_pathes, preface)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
136
163
|
# 名前のノートが存在しない場合
|
137
164
|
if file_pathes.nil?
|
138
|
-
return
|
139
|
-
|
140
|
-
return <<~EOS
|
141
|
-
Note not found: #{name}
|
142
|
-
However, I found other notes linked to this note.
|
143
|
-
#{file_pathes.map { |file_path| "- #{file_path}" }.join("\n")}
|
144
|
-
EOS
|
165
|
+
return note_not_found(name) if @links_by_file_name[name].nil?
|
166
|
+
return list_links(name, preface)
|
145
167
|
end
|
146
|
-
|
168
|
+
|
169
|
+
open_file(name, file_pathes, preface)
|
170
|
+
end
|
171
|
+
|
172
|
+
private def note_not_found name
|
173
|
+
return <<~EOS
|
174
|
+
Note not found: #{name}
|
175
|
+
EOS
|
176
|
+
end
|
177
|
+
|
178
|
+
private def list_links name, preface
|
179
|
+
return <<~EOS
|
180
|
+
#{preface}
|
181
|
+
Note not found: #{name}
|
182
|
+
However, I found other notes linked to this note.
|
183
|
+
#{@links_by_file_name[name].shuffle.map { |file_path| "- #{File.basename(file_path, '.md')}" }.join("\n")}
|
184
|
+
EOS
|
185
|
+
end
|
186
|
+
|
187
|
+
private def open_file name, file_pathes, preface
|
147
188
|
# 複数のファイルがある場合は、---とファイル名で区切って返す
|
148
|
-
file_pathes.map do |file_path|
|
189
|
+
result = file_pathes.map do |file_path|
|
149
190
|
content = open(file_path) { |f| f.read.force_encoding('UTF-8') }
|
150
191
|
link_notes = if @links_by_file_path[file_path].nil?
|
151
192
|
""
|
@@ -155,16 +196,16 @@ module ObsidianFetch
|
|
155
196
|
#{(@links_by_file_path[file_path] || []).shuffle.map { |file_path| "- #{File.basename(file_path, '.md')}" }.join("\n")}
|
156
197
|
EOS
|
157
198
|
end
|
158
|
-
|
199
|
+
metadata = <<~EOS
|
159
200
|
The contents of the note '#{name}' is as follows.
|
160
201
|
#{link_notes}
|
161
202
|
---
|
162
|
-
|
163
203
|
EOS
|
164
|
-
|
204
|
+
metadata + content
|
165
205
|
end.join("\n\n---\n\n")
|
206
|
+
preface + result
|
166
207
|
end
|
167
|
-
|
208
|
+
|
168
209
|
MAX_LIST_SIZE = 20
|
169
210
|
def tool_list name
|
170
211
|
name = Vault.normalize_note_name(name)
|
metadata
CHANGED
@@ -1,30 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obsidian_fetch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sou7
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: mcp
|
13
|
+
name: mcp
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
16
|
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
18
|
+
version: 0.2.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
27
|
-
description:
|
25
|
+
version: 0.2.0
|
28
26
|
email:
|
29
27
|
- soukouki0@yahoo.co.jp
|
30
28
|
executables:
|
@@ -37,6 +35,7 @@ files:
|
|
37
35
|
- README.md
|
38
36
|
- Rakefile
|
39
37
|
- exe/obsidian_fetch
|
38
|
+
- lib/exe.rb
|
40
39
|
- lib/obsidian_fetch.rb
|
41
40
|
- lib/obsidian_fetch/version.rb
|
42
41
|
- sig/obsidian_fetch.rbs
|
@@ -47,7 +46,6 @@ metadata:
|
|
47
46
|
homepage_uri: https://ob.sou7.io/2025-04/week17/obsidian_fetch
|
48
47
|
source_code_uri: https://github.com/soukouki/obsidian_fetch
|
49
48
|
changelog_uri: https://github.com/soukouki/obsidian_fetch/blob/main/CHANGELOG.md
|
50
|
-
post_install_message:
|
51
49
|
rdoc_options: []
|
52
50
|
require_paths:
|
53
51
|
- lib
|
@@ -62,8 +60,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
60
|
- !ruby/object:Gem::Version
|
63
61
|
version: '0'
|
64
62
|
requirements: []
|
65
|
-
rubygems_version: 3.
|
66
|
-
signing_key:
|
63
|
+
rubygems_version: 3.6.7
|
67
64
|
specification_version: 4
|
68
|
-
summary: MCP servers
|
65
|
+
summary: MCP servers focused on fetching and presenting information from Obsidian
|
66
|
+
vaults
|
69
67
|
test_files: []
|