Skip to content

Use 120 characters instead of 88 #856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
36 changes: 9 additions & 27 deletions examples/fastmcp/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,14 @@

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](
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -152,9 +146,7 @@ async def merge_with(self, other: Self, deps: Deps):
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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -273,25 +263,19 @@ 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


@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()

Expand All @@ -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("""
Expand Down
8 changes: 2 additions & 6 deletions examples/fastmcp/text_me.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@


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

Expand Down
5 changes: 1 addition & 4 deletions examples/fastmcp/unicode_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
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:
Expand Down
31 changes: 8 additions & 23 deletions examples/servers/simple-auth/mcp_simple_auth/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,15 @@ async def register_client(self, client_info: OAuthClientInformationFull):
"""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)

# Store the state mapping
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,
}

Expand All @@ -117,9 +113,7 @@ async def handle_github_callback(self, code: str, state: str) -> str:

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
Expand Down Expand Up @@ -200,8 +194,7 @@ async def exchange_authorization_code(
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,
)
Expand Down Expand Up @@ -232,9 +225,7 @@ async def load_access_token(self, token: str) -> AccessToken | None:

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

Expand All @@ -247,9 +238,7 @@ async def exchange_refresh_token(
"""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]
Expand Down Expand Up @@ -335,9 +324,7 @@ async def get_user_profile() -> dict[str, Any]:
)

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()

Expand All @@ -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}")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ select = ["C4", "E", "F", "I", "PERF", "UP"]
ignore = ["PERF203"]

[tool.ruff]
line-length = 88
line-length = 120
target-version = "py310"

[tool.ruff.lint.per-file-ignores]
Expand Down
12 changes: 3 additions & 9 deletions src/mcp/cli/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ def get_claude_config_path() -> Path | None:
elif sys.platform == "darwin":
path = Path(Path.home(), "Library", "Application Support", "Claude")
elif sys.platform.startswith("linux"):
path = Path(
os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude"
)
path = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"), "Claude")
else:
return None

Expand All @@ -37,8 +35,7 @@ def get_uv_path() -> str:
uv_path = shutil.which("uv")
if not uv_path:
logger.error(
"uv executable not found in PATH, falling back to 'uv'. "
"Please ensure uv is installed and in your PATH"
"uv executable not found in PATH, falling back to 'uv'. " "Please ensure uv is installed and in your PATH"
)
return "uv" # Fall back to just "uv" if not found
return uv_path
Expand Down Expand Up @@ -94,10 +91,7 @@ def update_claude_config(
config["mcpServers"] = {}

# Always preserve existing env vars and merge with new ones
if (
server_name in config["mcpServers"]
and "env" in config["mcpServers"][server_name]
):
if server_name in config["mcpServers"] and "env" in config["mcpServers"][server_name]:
existing_env = config["mcpServers"][server_name]["env"]
if env_vars:
# New vars take precedence over existing ones
Expand Down
36 changes: 9 additions & 27 deletions src/mcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def _get_npx_command():
# Try both npx.cmd and npx.exe on Windows
for cmd in ["npx.cmd", "npx.exe", "npx"]:
try:
subprocess.run(
[cmd, "--version"], check=True, capture_output=True, shell=True
)
subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True)
return cmd
except subprocess.CalledProcessError:
continue
Expand All @@ -58,9 +56,7 @@ def _get_npx_command():
def _parse_env_var(env_var: str) -> tuple[str, str]:
"""Parse environment variable string in format KEY=VALUE."""
if "=" not in env_var:
logger.error(
f"Invalid environment variable format: {env_var}. Must be KEY=VALUE"
)
logger.error(f"Invalid environment variable format: {env_var}. Must be KEY=VALUE")
sys.exit(1)
key, value = env_var.split("=", 1)
return key.strip(), value.strip()
Expand Down Expand Up @@ -154,14 +150,10 @@ def _check_server_object(server_object: Any, object_name: str):
True if it's supported.
"""
if not isinstance(server_object, FastMCP):
logger.error(
f"The server object {object_name} is of type "
f"{type(server_object)} (expecting {FastMCP})."
)
logger.error(f"The server object {object_name} is of type " f"{type(server_object)} (expecting {FastMCP}).")
if isinstance(server_object, LowLevelServer):
logger.warning(
"Note that only FastMCP server is supported. Low level "
"Server class is not yet supported."
"Note that only FastMCP server is supported. Low level " "Server class is not yet supported."
)
return False
return True
Expand All @@ -172,10 +164,7 @@ def _check_server_object(server_object: Any, object_name: str):
for name in ["mcp", "server", "app"]:
if hasattr(module, name):
if not _check_server_object(getattr(module, name), f"{file}:{name}"):
logger.error(
f"Ignoring object '{file}:{name}' as it's not a valid "
"server object"
)
logger.error(f"Ignoring object '{file}:{name}' as it's not a valid " "server object")
continue
return getattr(module, name)

Expand Down Expand Up @@ -280,8 +269,7 @@ def dev(
npx_cmd = _get_npx_command()
if not npx_cmd:
logger.error(
"npx not found. Please ensure Node.js and npm are properly installed "
"and added to your system PATH."
"npx not found. Please ensure Node.js and npm are properly installed " "and added to your system PATH."
)
sys.exit(1)

Expand Down Expand Up @@ -383,8 +371,7 @@ def install(
typer.Option(
"--name",
"-n",
help="Custom name for the server (defaults to server's name attribute or"
" file name)",
help="Custom name for the server (defaults to server's name attribute or" " file name)",
),
] = None,
with_editable: Annotated[
Expand Down Expand Up @@ -458,8 +445,7 @@ def install(
name = server.name
except (ImportError, ModuleNotFoundError) as e:
logger.debug(
"Could not import server (likely missing dependencies), using file"
" name",
"Could not import server (likely missing dependencies), using file" " name",
extra={"error": str(e)},
)
name = file.stem
Expand All @@ -477,11 +463,7 @@ def install(
if env_file:
if dotenv:
try:
env_dict |= {
k: v
for k, v in dotenv.dotenv_values(env_file).items()
if v is not None
}
env_dict |= {k: v for k, v in dotenv.dotenv_values(env_file).items() if v is not None}
except Exception as e:
logger.error(f"Failed to load .env file: {e}")
sys.exit(1)
Expand Down
8 changes: 2 additions & 6 deletions src/mcp/client/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@


async def message_handler(
message: RequestResponder[types.ServerRequest, types.ClientResult]
| types.ServerNotification
| Exception,
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, Exception):
logger.error("Error: %s", message)
Expand Down Expand Up @@ -60,9 +58,7 @@ async def main(command_or_url: str, args: list[str], env: list[tuple[str, str]])
await run_session(*streams)
else:
# Use stdio client for commands
server_parameters = StdioServerParameters(
command=command_or_url, args=args, env=env_dict
)
server_parameters = StdioServerParameters(command=command_or_url, args=args, env=env_dict)
async with stdio_client(server_parameters) as streams:
await run_session(*streams)

Expand Down
Loading
Loading