Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/exe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ class ReadTool < MCP::Tool

def self.call(name:)
# 名前が文字列でない場合
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }]) unless name.is_a?(String)
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }], error: true) unless name.is_a?(String)

# Vaultからノートを読み取る
result = $vault.tool_read(name)
MCP::Tool::Response.new([{ type: "text", text: result }])
MCP::Tool::Response.new([{ type: "text", text: result.text }], error: result.error)
end
end

Expand All @@ -117,11 +117,11 @@ class ListTool < MCP::Tool

def self.call(name:)
# 名前が文字列でない場合
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }]) unless name.is_a?(String)
return MCP::Tool::Response.new([{ type: "text", text: "Name must be a string" }], error: true) unless name.is_a?(String)

# Vaultからノートを検索
result = $vault.tool_list(name)
MCP::Tool::Response.new([{ type: "text", text: result }])
MCP::Tool::Response.new([{ type: "text", text: result.text }], error: result.error)
end
end

Expand Down
31 changes: 19 additions & 12 deletions lib/obsidian_fetch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@

module ObsidianFetch
class Error < StandardError; end


# ツール呼び出しの結果を保持する
Result = Struct.new(:text, :error, keyword_init: true) do
def initialize(text:, error: false)
super(text: text, error: error)
end
end

class Vault
attr_reader :notes, :links_by_file_path, :links_by_file_name

Expand Down Expand Up @@ -143,30 +150,30 @@ def tool_read name
# もしも名前で見つからなければ、リンクにも存在しないか確認する
link_pathes = @links_by_file_name[fixed_name]
if link_pathes.nil?
return note_not_found(name)
return Result.new(text: note_not_found(name), error: true)
else
# リンク先のノートが見つかった場合は、prefaceを追加する
preface = <<~EOS
Presumably a path was specified. The process was automatically renamed and processed.
EOS
return list_links(fixed_name, preface)
return Result.new(text: list_links(fixed_name, preface), error: true)
end
else
# ノート名が見つかった場合は、prefaceを追加する
preface = <<~EOS
Presumably a path was specified. The process was automatically renamed and processed.
EOS
return open_file(fixed_name, file_pathes, preface)
return Result.new(text: open_file(fixed_name, file_pathes, preface))
end
end

# 名前のノートが存在しない場合
if file_pathes.nil?
return note_not_found(name) if @links_by_file_name[name].nil?
return list_links(name, preface)
return Result.new(text: note_not_found(name), error: true) if @links_by_file_name[name].nil?
return Result.new(text: list_links(name, preface), error: true)
end

open_file(name, file_pathes, preface)
Result.new(text: open_file(name, file_pathes, preface))
end

private def note_not_found name
Expand Down Expand Up @@ -209,11 +216,11 @@ def tool_read name
MAX_LIST_SIZE = 20
def tool_list name
name = Vault.normalize_note_name(name)
split_name = name.split(/[\s ]+/)
split_name = name.split(/[\s ]+/)
# 空白文字で検索された場合は失敗した旨を返す
# 仮に全ノートからランダムなMAX_LIST_SIZEを返してしまうと、LLMが誤って呼び出した場合に偽の関連性を持ってしまうため
if split_name.empty?
return <<~EOS
return Result.new(<<~EOS, error: true)
It cannot be listed in a blank string.
EOS
end
Expand All @@ -223,11 +230,11 @@ def tool_list name
# 名前で検索したが見つからない場合
if matched_notes.empty?
has_found_link = @links_by_file_name[name] && !@links_by_file_name[name].empty?
return <<~EOS unless has_found_link
return Result.new(text: <<~EOS, error: true) unless has_found_link
Note not found: #{name}
Search again with a substring or a string with a different notation.
EOS
return <<~EOS
return Result.new(text: <<~EOS, error: true)
Note not found: #{name}
However, I found other notes linked to this note.
#{@links_by_file_name[name].shuffle.map { |file_path| "- #{File.basename(file_path, '.md')}" }.join("\n")}
Expand All @@ -243,7 +250,7 @@ def tool_list name
list = matched_notes.keys.shuffle.map do |note_name|
"- #{note_name}"
end.join("\n")
preface + list
Result.new(text: preface + list)
end
end

Expand Down
50 changes: 49 additions & 1 deletion test/test_mcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ def test_read_not_found
assert content.start_with?('Note not found:'), "エラーメッセージが期待どおりではありません: #{content}"
end

def test_read_not_found_is_error
read_tool = @client.tools.find { |t| t.name == 'read_tool' }

response = @client.call_tool(
tool: read_tool,
arguments: { name: 'NonExistent' }
)

# isError が true であることを確認
assert response['result']['isError'], "isError が true ではありません: #{response['result']}"
end

def test_read_hello_is_not_error
read_tool = @client.tools.find { |t| t.name == 'read_tool' }

response = @client.call_tool(
tool: read_tool,
arguments: { name: 'Hello World' }
)

# isError が false または未設定であることを確認
refute response['result']['isError'], "isError が設定されています: #{response['result']}"
end

def test_list_hello
list_tool = @client.tools.find { |t| t.name == 'list_tool' }
refute_nil list_tool, 'list ツールが見つかりませんでした'
Expand Down Expand Up @@ -85,6 +109,30 @@ def test_list_not_found
assert content.start_with?('Note not found:'), "エラーメッセージが期待どおりではありません: #{content}"
end

def test_list_not_found_is_error
list_tool = @client.tools.find { |t| t.name == 'list_tool' }

response = @client.call_tool(
tool: list_tool,
arguments: { name: 'NonExistent' }
)

# isError が true であることを確認
assert response['result']['isError'], "isError が true ではありません: #{response['result']}"
end

def test_list_hello_is_not_error
list_tool = @client.tools.find { |t| t.name == 'list_tool' }

response = @client.call_tool(
tool: list_tool,
arguments: { name: 'Hello' }
)

# isError が false または未設定であることを確認
refute response['result']['isError'], "isError が設定されています: #{response['result']}"
end

def test_backlink
read_tool = @client.tools.find { |t| t.name == 'read_tool' }

Expand All @@ -98,4 +146,4 @@ def test_backlink
refute content.start_with?('Note not found:'), "ノートが見つかりませんでした: #{content}"
assert content.include?('Hello World'), "バックリンクが見つかりませんでした"
end
end
end
Loading