Compare commits

...

4 Commits

Author SHA1 Message Date
phernandez 2c29dcc2b2 chore: update version to 0.14.2 for v0.14.2 release 2025-07-03 17:30:40 -05:00
phernandez 448210e552 docs: add v0.14.2 changelog entry
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-03 17:23:54 -05:00
Drew Cain 3621bb7b4d fix: MCP Error with MCP-Hub #204 (#212)
Signed-off-by: Drew Cain <groksrc@gmail.com>
2025-07-03 16:57:43 -05:00
Drew Cain f80ac0ee72 fix: replace deprecated datetime.utcnow() with timezone-aware alternatives and suppress SQLAlchemy warnings (#211)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2025-07-03 16:57:30 -05:00
7 changed files with 73 additions and 20 deletions
+46
View File
@@ -1,5 +1,51 @@
# CHANGELOG
## v0.14.2 (2025-07-03)
### Bug Fixes
- **#204**: Fix MCP Error with MCP-Hub integration
([`3621bb7`](https://github.com/basicmachines-co/basic-memory/commit/3621bb7b4d6ac12d892b18e36bb8f7c9101c7b10))
- Resolve compatibility issues with MCP-Hub
- Improve error handling in project management tools
- Ensure stable MCP tool integration across different environments
- **Modernize datetime handling and suppress SQLAlchemy warnings**
([`f80ac0e`](https://github.com/basicmachines-co/basic-memory/commit/f80ac0e3e74b7a737a7fc7b956b5c1d61b0c67b8))
- Replace deprecated `datetime.utcnow()` with timezone-aware alternatives
- Suppress SQLAlchemy deprecation warnings for cleaner output
- Improve future compatibility with Python datetime best practices
## v0.14.1 (2025-07-03)
### Bug Fixes
- **#203**: Constrain fastmcp version to prevent breaking changes
([`827f7cf`](https://github.com/basicmachines-co/basic-memory/commit/827f7cf86e7b84c56e7a43bb83f2e5d84a1ad8b8))
- Pin fastmcp to compatible version range to avoid API breaking changes
- Ensure stable MCP server functionality across updates
- Improve dependency management for production deployments
- **#190**: Fix Problems with MCP integration
([`bd4f551`](https://github.com/basicmachines-co/basic-memory/commit/bd4f551a5bb0b7b4d3a5b04de70e08987c6ab2f9))
- Resolve MCP server initialization and communication issues
- Improve error handling and recovery in MCP operations
- Enhance stability for AI assistant integrations
### Features
- **Add Cursor IDE integration button** - One-click setup for Cursor IDE users
([`5360005`](https://github.com/basicmachines-co/basic-memory/commit/536000512294d66090bf87abc8014f4dfc284310))
- Direct installation button for Cursor IDE in README
- Streamlined setup process for Cursor users
- Enhanced developer experience for AI-powered coding
- **Add Homebrew installation instructions** - Official Homebrew tap support
([`39f811f`](https://github.com/basicmachines-co/basic-memory/commit/39f811f8b57dd998445ae43537cd492c680b2e11))
- Official Homebrew formula in basicmachines-co/basic-memory tap
- Simplified installation process for macOS users
- Package manager integration for easier dependency management
## v0.14.0 (2025-06-26)
### Features
+1 -1
View File
@@ -1,7 +1,7 @@
"""basic-memory - Local-first knowledge management combining Zettelkasten with knowledge graphs"""
# Package version - updated by release automation
__version__ = "0.14.1"
__version__ = "0.14.2"
# API version for FastAPI - independent of package version
__api_version__ = "v0"
+8 -8
View File
@@ -1,7 +1,7 @@
"""OAuth authentication provider for Basic Memory MCP server."""
import secrets
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Dict, Optional
import jwt
@@ -92,7 +92,7 @@ class BasicMemoryOAuthProvider(
self.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
code=auth_code,
scopes=params.scopes or [],
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
client_id=client.client_id,
code_challenge=params.code_challenge,
redirect_uri=params.redirect_uri,
@@ -119,7 +119,7 @@ class BasicMemoryOAuthProvider(
if code and code.client_id == client.client_id:
# Check if expired
if datetime.utcnow().timestamp() > code.expires_at:
if datetime.now(timezone.utc).timestamp() > code.expires_at:
del self.authorization_codes[authorization_code]
return None
return code
@@ -135,7 +135,7 @@ class BasicMemoryOAuthProvider(
refresh_token = secrets.token_urlsafe(32)
# Store tokens
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
self.access_tokens[access_token] = BasicMemoryAccessToken(
token=access_token,
@@ -187,7 +187,7 @@ class BasicMemoryOAuthProvider(
new_refresh_token = secrets.token_urlsafe(32)
# Store new tokens
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
self.access_tokens[new_access_token] = BasicMemoryAccessToken(
token=new_access_token,
@@ -220,7 +220,7 @@ class BasicMemoryOAuthProvider(
if access_token:
# Check if expired
if access_token.expires_at and datetime.utcnow().timestamp() > access_token.expires_at:
if access_token.expires_at and datetime.now(timezone.utc).timestamp() > access_token.expires_at:
logger.debug("Token found in memory but expired, removing")
del self.access_tokens[token]
return None
@@ -262,8 +262,8 @@ class BasicMemoryOAuthProvider(
"iss": self.issuer_url,
"sub": client_id,
"aud": "basic-memory",
"exp": datetime.utcnow() + timedelta(hours=1),
"iat": datetime.utcnow(),
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
"iat": datetime.now(timezone.utc),
"scopes": scopes,
}
@@ -3,7 +3,7 @@
import os
import secrets
from dataclasses import dataclass
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any
import httpx
@@ -123,7 +123,7 @@ class SupabaseOAuthProvider(
self.pending_auth_codes[state] = SupabaseAuthorizationCode(
code=state,
scopes=params.scopes or [],
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
client_id=client.client_id,
code_challenge=params.code_challenge,
redirect_uri=params.redirect_uri,
@@ -218,7 +218,7 @@ class SupabaseOAuthProvider(
if code and code.client_id == client.client_id:
# Check expiration
if datetime.utcnow().timestamp() > code.expires_at:
if datetime.now(timezone.utc).timestamp() > code.expires_at:
del self.pending_auth_codes[authorization_code]
return None
return code
@@ -453,8 +453,8 @@ class SupabaseOAuthProvider(
"email": email,
"scopes": scopes,
"supabase_token": supabase_access_token[:10] + "...", # Reference only
"exp": datetime.utcnow() + timedelta(hours=1),
"iat": datetime.utcnow(),
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
"iat": datetime.now(timezone.utc),
}
# Use Supabase JWT secret if available
@@ -5,6 +5,7 @@ and manage project context during conversations.
"""
from textwrap import dedent
from typing import Optional
from fastmcp import Context
from loguru import logger
@@ -19,7 +20,9 @@ from basic_memory.utils import generate_permalink
@mcp.tool("list_memory_projects")
async def list_memory_projects(ctx: Context | None = None) -> str:
async def list_memory_projects(
ctx: Context | None = None, _compatibility: Optional[str] = None
) -> str:
"""List all available projects with their status.
Shows all Basic Memory projects that are available, indicating which one
@@ -159,7 +162,9 @@ async def switch_project(project_name: str, ctx: Context | None = None) -> str:
@mcp.tool()
async def get_current_project(ctx: Context | None = None) -> str:
async def get_current_project(
ctx: Context | None = None, _compatibility: Optional[str] = None
) -> str:
"""Show the currently active project and basic stats.
Displays which project is currently active and provides basic information
+2
View File
@@ -173,6 +173,8 @@ def setup_logging(
"httpx": logging.WARNING,
# File watching logs
"watchfiles.main": logging.WARNING,
# SQLAlchemy deprecation warnings
"sqlalchemy": logging.WARNING,
}
# Set log levels for noisy loggers
+4 -4
View File
@@ -1,7 +1,7 @@
"""Tests for OAuth authentication provider."""
import pytest
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from mcp.server.auth.provider import AuthorizationParams
from mcp.shared.auth import OAuthClientInformationFull
from pydantic import AnyHttpUrl
@@ -185,7 +185,7 @@ class TestBasicMemoryOAuthProvider:
token=token_str,
client_id=client.client_id,
scopes=["read", "write"],
expires_at=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
expires_at=int((datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()),
)
provider.access_tokens[token_str] = access_token
@@ -226,7 +226,7 @@ class TestBasicMemoryOAuthProvider:
provider.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
code=auth_code,
scopes=["read"],
expires_at=(datetime.utcnow() - timedelta(minutes=1)).timestamp(),
expires_at=(datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp(),
client_id=client.client_id,
code_challenge="challenge",
redirect_uri=AnyHttpUrl("http://localhost:3000/callback"),
@@ -288,7 +288,7 @@ class TestBasicMemoryOAuthProvider:
token=expired_token_str,
client_id="test-client",
scopes=["read"],
expires_at=int((datetime.utcnow() - timedelta(minutes=1)).timestamp()), # Expired
expires_at=int((datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp()), # Expired
)
provider.access_tokens[expired_token_str] = expired_access_token