Compare commits
47 Commits
main
...
c58449ca85
Author | SHA1 | Date | |
---|---|---|---|
c58449ca85 | |||
|
4aac5c2b87 | ||
|
a7469914ec | ||
|
551d3037bd | ||
|
49b51323b0 | ||
|
ccd3be1adf | ||
|
4d96b36a6b | ||
|
306eb2b406 | ||
|
7211cb0b2f | ||
|
e6a02957e1 | ||
|
a32eb48095 | ||
|
d42d7454be | ||
|
d7f16416dd | ||
|
0133bdd91f | ||
|
8601e44502 | ||
|
3705e37911 | ||
|
8ddc50cc2f | ||
|
87059bd9ba | ||
|
f563e9a43c | ||
|
c33c711c68 | ||
|
0de513e993 | ||
|
4295a17ab8 | ||
|
4b518efea7 | ||
|
b4596a6028 | ||
|
ed9bffe128 | ||
|
f44ebaa441 | ||
|
6b02ecdf71 | ||
|
33c931280f | ||
|
b219c45d17 | ||
|
95de950588 | ||
|
befe6fa403 | ||
|
c4765c3fc5 | ||
|
f14b4085e2 | ||
|
07551d0b5a | ||
|
1a3b173e18 | ||
|
c7cd449c7f | ||
|
38845f30c8 | ||
|
90ad7a682d | ||
|
a27363c643 | ||
|
616309c2cb | ||
|
e70e940c57 | ||
|
120c435723 | ||
|
944819a3a1 | ||
|
e3d7db5d20 | ||
|
ab7ee87e56 | ||
|
0aca93fc62 | ||
|
f3d247af0a |
@@ -11,7 +11,6 @@ class Obsidian():
|
|||||||
protocol: str = os.getenv('OBSIDIAN_PROTOCOL', 'https').lower(),
|
protocol: str = os.getenv('OBSIDIAN_PROTOCOL', 'https').lower(),
|
||||||
host: str = str(os.getenv('OBSIDIAN_HOST', '127.0.0.1')),
|
host: str = str(os.getenv('OBSIDIAN_HOST', '127.0.0.1')),
|
||||||
port: int = int(os.getenv('OBSIDIAN_PORT', '27124')),
|
port: int = int(os.getenv('OBSIDIAN_PORT', '27124')),
|
||||||
path: str = '',
|
|
||||||
verify_ssl: bool = False,
|
verify_ssl: bool = False,
|
||||||
):
|
):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
@@ -23,7 +22,6 @@ class Obsidian():
|
|||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.path = path.rstrip('/')
|
|
||||||
self.verify_ssl = verify_ssl
|
self.verify_ssl = verify_ssl
|
||||||
self.timeout = (3, 6)
|
self.timeout = (3, 6)
|
||||||
|
|
||||||
@@ -54,7 +52,6 @@ class Obsidian():
|
|||||||
protocol = parsed.scheme
|
protocol = parsed.scheme
|
||||||
host = parsed.hostname
|
host = parsed.hostname
|
||||||
port = parsed.port
|
port = parsed.port
|
||||||
path = parsed.path
|
|
||||||
|
|
||||||
# Set default ports based on protocol if not specified
|
# Set default ports based on protocol if not specified
|
||||||
if port is None:
|
if port is None:
|
||||||
@@ -65,21 +62,20 @@ class Obsidian():
|
|||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
path=path,
|
|
||||||
verify_ssl=verify_ssl
|
verify_ssl=verify_ssl
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Failed to parse OBSIDIAN_HOST URL '{url}': {str(e)}")
|
raise ValueError(f"Failed to parse OBSIDIAN_HOST URL '{url}': {str(e)}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_host_config(host_config: str) -> Tuple[str, str, int, str]:
|
def parse_host_config(host_config: str) -> Tuple[str, str, int]:
|
||||||
"""Parse host configuration string.
|
"""Parse host configuration string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
host_config: Either a full URL (http://host:port) or just hostname/IP
|
host_config: Either a full URL (http://host:port) or just hostname/IP
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (protocol, host, port, path)
|
Tuple of (protocol, host, port)
|
||||||
"""
|
"""
|
||||||
if '://' in host_config:
|
if '://' in host_config:
|
||||||
# Full URL format
|
# Full URL format
|
||||||
@@ -87,28 +83,16 @@ class Obsidian():
|
|||||||
protocol = parsed.scheme or 'https'
|
protocol = parsed.scheme or 'https'
|
||||||
host = parsed.hostname or '127.0.0.1'
|
host = parsed.hostname or '127.0.0.1'
|
||||||
port = parsed.port or (27124 if protocol == 'https' else 27123)
|
port = parsed.port or (27124 if protocol == 'https' else 27123)
|
||||||
path = parsed.path
|
|
||||||
else:
|
else:
|
||||||
# Support legacy formats
|
# Legacy hostname/IP only format
|
||||||
# 1) hostname/IP only
|
protocol = 'https'
|
||||||
# 2) hostname:port (no protocol)
|
host = host_config
|
||||||
if ':' in host_config:
|
port = 27124
|
||||||
# Treat as host:port and default protocol to https
|
|
||||||
parsed = urlparse(f'https://{host_config}')
|
|
||||||
protocol = 'https'
|
|
||||||
host = parsed.hostname or '127.0.0.1'
|
|
||||||
port = parsed.port or 27124
|
|
||||||
path = ''
|
|
||||||
else:
|
|
||||||
protocol = 'https'
|
|
||||||
host = host_config
|
|
||||||
port = 27124
|
|
||||||
path = ''
|
|
||||||
|
|
||||||
return protocol, host, port, path
|
return protocol, host, port
|
||||||
|
|
||||||
def get_base_url(self) -> str:
|
def get_base_url(self) -> str:
|
||||||
return f'{self.protocol}://{self.host}:{self.port}{self.path}'
|
return f'{self.protocol}://{self.host}:{self.port}'
|
||||||
|
|
||||||
def _get_headers(self) -> dict:
|
def _get_headers(self) -> dict:
|
||||||
headers = {
|
headers = {
|
||||||
@@ -313,7 +297,7 @@ class Obsidian():
|
|||||||
Returns:
|
Returns:
|
||||||
List of recent periodic notes
|
List of recent periodic notes
|
||||||
"""
|
"""
|
||||||
url = f"{self.get_base_url()}/periodic/{period}/recent/"
|
url = f"{self.get_base_url()}/periodic/{period}/recent"
|
||||||
params = {
|
params = {
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"includeContent": include_content
|
"includeContent": include_content
|
||||||
|
@@ -5,8 +5,7 @@ from functools import lru_cache
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from mcp.server import Server as MCPServer
|
from mcp.server import Server
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from mcp.types import (
|
from mcp.types import (
|
||||||
Tool,
|
Tool,
|
||||||
TextContent,
|
TextContent,
|
||||||
@@ -16,7 +15,6 @@ from mcp.types import (
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
from . import obsidian
|
|
||||||
from . import tools
|
from . import tools
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
@@ -25,17 +23,11 @@ from . import tools
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger("mcp-obsidian")
|
logger = logging.getLogger("mcp-obsidian")
|
||||||
|
|
||||||
@asynccontextmanager
|
api_key = os.getenv("OBSIDIAN_API_KEY")
|
||||||
async def lifespan(app: MCPServer):
|
if not api_key:
|
||||||
api_key = os.getenv("OBSIDIAN_API_KEY")
|
raise ValueError(f"OBSIDIAN_API_KEY environment variable required. Working directory: {os.getcwd()}")
|
||||||
if not api_key:
|
|
||||||
raise ValueError(f"OBSIDSIAN_API_KEY environment variable required. Working directory: {os.getcwd()}")
|
|
||||||
yield
|
|
||||||
|
|
||||||
app = MCPServer(
|
app = Server("mcp-obsidian")
|
||||||
"mcp-obsidian",
|
|
||||||
lifespan=lifespan,
|
|
||||||
)
|
|
||||||
|
|
||||||
tool_handlers = {}
|
tool_handlers = {}
|
||||||
def add_tool_handler(tool_class: tools.ToolHandler):
|
def add_tool_handler(tool_class: tools.ToolHandler):
|
||||||
|
@@ -9,6 +9,19 @@ import json
|
|||||||
import os
|
import os
|
||||||
from . import obsidian
|
from . import obsidian
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
api_key = os.getenv("OBSIDIAN_API_KEY", "")
|
||||||
|
obsidian_host = os.getenv("OBSIDIAN_HOST", "https://127.0.0.1:27124")
|
||||||
|
|
||||||
|
if api_key == "":
|
||||||
|
raise ValueError(f"OBSIDIAN_API_KEY environment variable required. Working directory: {os.getcwd()}")
|
||||||
|
|
||||||
|
# Parse the OBSIDIAN_HOST configuration at module level for validation
|
||||||
|
try:
|
||||||
|
protocol, host, port = obsidian.Obsidian.parse_host_config(obsidian_host)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"Invalid OBSIDIAN_HOST configuration: {str(e)}")
|
||||||
|
|
||||||
def create_obsidian_api() -> obsidian.Obsidian:
|
def create_obsidian_api() -> obsidian.Obsidian:
|
||||||
"""Factory function to create Obsidian API instances.
|
"""Factory function to create Obsidian API instances.
|
||||||
|
|
||||||
@@ -21,26 +34,12 @@ def create_obsidian_api() -> obsidian.Obsidian:
|
|||||||
Raises:
|
Raises:
|
||||||
Exception: If configuration is invalid or instance creation fails
|
Exception: If configuration is invalid or instance creation fails
|
||||||
"""
|
"""
|
||||||
# Load environment variables
|
|
||||||
api_key = os.getenv("OBSIDIAN_API_KEY", "")
|
|
||||||
obsidian_host = os.getenv("OBSIDIAN_HOST", "https://127.0.0.1:27124")
|
|
||||||
|
|
||||||
if api_key == "":
|
|
||||||
raise ValueError(f"OBSIDIAN_API_KEY environment variable required. Working directory: {os.getcwd()}")
|
|
||||||
|
|
||||||
# Parse the OBSIDIAN_HOST configuration at module level for validation
|
|
||||||
try:
|
|
||||||
protocol, host, port, path = obsidian.Obsidian.parse_host_config(obsidian_host)
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError(f"Invalid OBSIDIAN_HOST configuration: {str(e)}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return obsidian.Obsidian(
|
return obsidian.Obsidian(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
path=path,
|
|
||||||
verify_ssl=False # Default to False for local development
|
verify_ssl=False # Default to False for local development
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -344,7 +343,7 @@ class PutContentToolHandler(ToolHandler):
|
|||||||
if "filepath" not in args or "content" not in args:
|
if "filepath" not in args or "content" not in args:
|
||||||
raise RuntimeError("filepath and content arguments required")
|
raise RuntimeError("filepath and content arguments required")
|
||||||
|
|
||||||
api = create_obsidian_api()
|
api = obsidian.Obsidian(api_key=api_key, host=obsidian_host)
|
||||||
api.put_content(args.get("filepath", ""), args["content"])
|
api.put_content(args.get("filepath", ""), args["content"])
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -542,8 +541,13 @@ class PeriodicNotesToolHandler(ToolHandler):
|
|||||||
if type not in valid_types:
|
if type not in valid_types:
|
||||||
raise RuntimeError(f"Invalid type: {type}. Must be one of: {', '.join(valid_types)}")
|
raise RuntimeError(f"Invalid type: {type}. Must be one of: {', '.join(valid_types)}")
|
||||||
|
|
||||||
|
<<<<<<< ours
|
||||||
api = create_obsidian_api()
|
api = create_obsidian_api()
|
||||||
content = api.get_periodic_note(period)
|
content = api.get_periodic_note(period)
|
||||||
|
=======
|
||||||
|
api = create_obsidian_api()
|
||||||
|
content = api.get_periodic_note(period)
|
||||||
|
>>>>>>> theirs
|
||||||
|
|
||||||
return [
|
return [
|
||||||
TextContent(
|
TextContent(
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
# Add the src directory to the Python path
|
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
|
|
||||||
|
|
||||||
from mcp_obsidian import obsidian
|
|
||||||
|
|
||||||
class TestObsidianIntegration(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up the environment variables for the test."""
|
|
||||||
os.environ['OBSIDIAN_API_KEY'] = 'REDACTED_API_KEY'
|
|
||||||
os.environ['OBSIDIAN_HOST'] = 'http://obsidian.obsidian.svc.cluster.local:27123'
|
|
||||||
|
|
||||||
def test_connection(self):
|
|
||||||
"""Test the connection to the Obsidian API."""
|
|
||||||
try:
|
|
||||||
protocol, host, port, path = obsidian.Obsidian.parse_host_config(os.environ['OBSIDIAN_HOST'])
|
|
||||||
|
|
||||||
api = obsidian.Obsidian(
|
|
||||||
api_key=os.environ['OBSIDIAN_API_KEY'],
|
|
||||||
protocol=protocol,
|
|
||||||
host=host,
|
|
||||||
port=port,
|
|
||||||
path=path,
|
|
||||||
verify_ssl=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Use a basic API call to verify the connection
|
|
||||||
files = api.list_files_in_vault()
|
|
||||||
|
|
||||||
self.assertIsNotNone(files)
|
|
||||||
print("Successfully connected to Obsidian API and listed files.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.fail(f"Failed to connect to Obsidian API: {e}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
Reference in New Issue
Block a user