Use 120 characters instead of 88 (#856)
This commit is contained in:
parent
f7265f7b91
commit
543961968c
4 changed files with 687 additions and 2142 deletions
|
|
@ -47,18 +47,14 @@ mcp = FastMCP(
|
|||
|
||||
DB_DSN = "postgresql://postgres:postgres@localhost:54320/memory_db"
|
||||
# reset memory with rm ~/.fastmcp/{USER}/memory/*
|
||||
PROFILE_DIR = (
|
||||
Path.home() / ".fastmcp" / os.environ.get("USER", "anon") / "memory"
|
||||
).resolve()
|
||||
PROFILE_DIR = (Path.home() / ".fastmcp" / os.environ.get("USER", "anon") / "memory").resolve()
|
||||
PROFILE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def cosine_similarity(a: list[float], b: list[float]) -> float:
|
||||
a_array = np.array(a, dtype=np.float64)
|
||||
b_array = np.array(b, dtype=np.float64)
|
||||
return np.dot(a_array, b_array) / (
|
||||
np.linalg.norm(a_array) * np.linalg.norm(b_array)
|
||||
)
|
||||
return np.dot(a_array, b_array) / (np.linalg.norm(a_array) * np.linalg.norm(b_array))
|
||||
|
||||
|
||||
async def do_ai[T](
|
||||
|
|
@ -97,9 +93,7 @@ class MemoryNode(BaseModel):
|
|||
summary: str = ""
|
||||
importance: float = 1.0
|
||||
access_count: int = 0
|
||||
timestamp: float = Field(
|
||||
default_factory=lambda: datetime.now(timezone.utc).timestamp()
|
||||
)
|
||||
timestamp: float = Field(default_factory=lambda: datetime.now(timezone.utc).timestamp())
|
||||
embedding: list[float]
|
||||
|
||||
@classmethod
|
||||
|
|
@ -152,9 +146,7 @@ class MemoryNode(BaseModel):
|
|||
self.importance += other.importance
|
||||
self.access_count += other.access_count
|
||||
self.embedding = [(a + b) / 2 for a, b in zip(self.embedding, other.embedding)]
|
||||
self.summary = await do_ai(
|
||||
self.content, "Summarize the following text concisely.", str, deps
|
||||
)
|
||||
self.summary = await do_ai(self.content, "Summarize the following text concisely.", str, deps)
|
||||
await self.save(deps)
|
||||
# Delete the merged node from the database
|
||||
if other.id is not None:
|
||||
|
|
@ -221,9 +213,7 @@ async def find_similar_memories(embedding: list[float], deps: Deps) -> list[Memo
|
|||
|
||||
async def update_importance(user_embedding: list[float], deps: Deps):
|
||||
async with deps.pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"SELECT id, importance, access_count, embedding FROM memories"
|
||||
)
|
||||
rows = await conn.fetch("SELECT id, importance, access_count, embedding FROM memories")
|
||||
for row in rows:
|
||||
memory_embedding = row["embedding"]
|
||||
similarity = cosine_similarity(user_embedding, memory_embedding)
|
||||
|
|
@ -273,9 +263,7 @@ async def display_memory_tree(deps: Deps) -> str:
|
|||
)
|
||||
result = ""
|
||||
for row in rows:
|
||||
effective_importance = row["importance"] * (
|
||||
1 + math.log(row["access_count"] + 1)
|
||||
)
|
||||
effective_importance = row["importance"] * (1 + math.log(row["access_count"] + 1))
|
||||
summary = row["summary"] or row["content"]
|
||||
result += f"- {summary} (Importance: {effective_importance:.2f})\n"
|
||||
return result
|
||||
|
|
@ -283,15 +271,11 @@ async def display_memory_tree(deps: Deps) -> str:
|
|||
|
||||
@mcp.tool()
|
||||
async def remember(
|
||||
contents: list[str] = Field(
|
||||
description="List of observations or memories to store"
|
||||
),
|
||||
contents: list[str] = Field(description="List of observations or memories to store"),
|
||||
):
|
||||
deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
|
||||
try:
|
||||
return "\n".join(
|
||||
await asyncio.gather(*[add_memory(content, deps) for content in contents])
|
||||
)
|
||||
return "\n".join(await asyncio.gather(*[add_memory(content, deps) for content in contents]))
|
||||
finally:
|
||||
await deps.pool.close()
|
||||
|
||||
|
|
@ -305,9 +289,7 @@ async def read_profile() -> str:
|
|||
|
||||
|
||||
async def initialize_database():
|
||||
pool = await asyncpg.create_pool(
|
||||
"postgresql://postgres:postgres@localhost:54320/postgres"
|
||||
)
|
||||
pool = await asyncpg.create_pool("postgresql://postgres:postgres@localhost:54320/postgres")
|
||||
try:
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("""
|
||||
|
|
|
|||
|
|
@ -28,15 +28,11 @@ from mcp.server.fastmcp import FastMCP
|
|||
|
||||
|
||||
class SurgeSettings(BaseSettings):
|
||||
model_config: SettingsConfigDict = SettingsConfigDict(
|
||||
env_prefix="SURGE_", env_file=".env"
|
||||
)
|
||||
model_config: SettingsConfigDict = SettingsConfigDict(env_prefix="SURGE_", env_file=".env")
|
||||
|
||||
api_key: str
|
||||
account_id: str
|
||||
my_phone_number: Annotated[
|
||||
str, BeforeValidator(lambda v: "+" + v if not v.startswith("+") else v)
|
||||
]
|
||||
my_phone_number: Annotated[str, BeforeValidator(lambda v: "+" + v if not v.startswith("+") else v)]
|
||||
my_first_name: str
|
||||
my_last_name: str
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ from mcp.server.fastmcp import FastMCP
|
|||
mcp = FastMCP()
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
description="🌟 A tool that uses various Unicode characters in its description: "
|
||||
"á é í ó ú ñ 漢字 🎉"
|
||||
)
|
||||
@mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: " "á é í ó ú ñ 漢字 🎉")
|
||||
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
|
||||
"""
|
||||
A simple tool that demonstrates Unicode handling in:
|
||||
|
|
|
|||
|
|
@ -82,9 +82,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
"""Register a new OAuth client."""
|
||||
self.clients[client_info.client_id] = client_info
|
||||
|
||||
async def authorize(
|
||||
self, client: OAuthClientInformationFull, params: AuthorizationParams
|
||||
) -> str:
|
||||
async def authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str:
|
||||
"""Generate an authorization URL for GitHub OAuth flow."""
|
||||
state = params.state or secrets.token_hex(16)
|
||||
|
||||
|
|
@ -92,9 +90,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
self.state_mapping[state] = {
|
||||
"redirect_uri": str(params.redirect_uri),
|
||||
"code_challenge": params.code_challenge,
|
||||
"redirect_uri_provided_explicitly": str(
|
||||
params.redirect_uri_provided_explicitly
|
||||
),
|
||||
"redirect_uri_provided_explicitly": str(params.redirect_uri_provided_explicitly),
|
||||
"client_id": client.client_id,
|
||||
}
|
||||
|
||||
|
|
@ -117,9 +113,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
|
||||
redirect_uri = state_data["redirect_uri"]
|
||||
code_challenge = state_data["code_challenge"]
|
||||
redirect_uri_provided_explicitly = (
|
||||
state_data["redirect_uri_provided_explicitly"] == "True"
|
||||
)
|
||||
redirect_uri_provided_explicitly = state_data["redirect_uri_provided_explicitly"] == "True"
|
||||
client_id = state_data["client_id"]
|
||||
|
||||
# Exchange code for token with GitHub
|
||||
|
|
@ -200,8 +194,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
for token, data in self.tokens.items()
|
||||
# see https://github.blog/engineering/platform-security/behind-githubs-new-authentication-token-formats/
|
||||
# which you get depends on your GH app setup.
|
||||
if (token.startswith("ghu_") or token.startswith("gho_"))
|
||||
and data.client_id == client.client_id
|
||||
if (token.startswith("ghu_") or token.startswith("gho_")) and data.client_id == client.client_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
|
@ -232,9 +225,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
|
||||
return access_token
|
||||
|
||||
async def load_refresh_token(
|
||||
self, client: OAuthClientInformationFull, refresh_token: str
|
||||
) -> RefreshToken | None:
|
||||
async def load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None:
|
||||
"""Load a refresh token - not supported."""
|
||||
return None
|
||||
|
||||
|
|
@ -247,9 +238,7 @@ class SimpleGitHubOAuthProvider(OAuthAuthorizationServerProvider):
|
|||
"""Exchange refresh token"""
|
||||
raise NotImplementedError("Not supported")
|
||||
|
||||
async def revoke_token(
|
||||
self, token: str, token_type_hint: str | None = None
|
||||
) -> None:
|
||||
async def revoke_token(self, token: str, token_type_hint: str | None = None) -> None:
|
||||
"""Revoke a token."""
|
||||
if token in self.tokens:
|
||||
del self.tokens[token]
|
||||
|
|
@ -335,9 +324,7 @@ def create_simple_mcp_server(settings: ServerSettings) -> FastMCP:
|
|||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise ValueError(
|
||||
f"GitHub API error: {response.status_code} - {response.text}"
|
||||
)
|
||||
raise ValueError(f"GitHub API error: {response.status_code} - {response.text}")
|
||||
|
||||
return response.json()
|
||||
|
||||
|
|
@ -361,9 +348,7 @@ def main(port: int, host: str, transport: Literal["sse", "streamable-http"]) ->
|
|||
# No hardcoded credentials - all from environment variables
|
||||
settings = ServerSettings(host=host, port=port)
|
||||
except ValueError as e:
|
||||
logger.error(
|
||||
"Failed to load settings. Make sure environment variables are set:"
|
||||
)
|
||||
logger.error("Failed to load settings. Make sure environment variables are set:")
|
||||
logger.error(" MCP_GITHUB_GITHUB_CLIENT_ID=<your-client-id>")
|
||||
logger.error(" MCP_GITHUB_GITHUB_CLIENT_SECRET=<your-client-secret>")
|
||||
logger.error(f"Error: {e}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue