feat: Add Smithery CLI deployment support
- Add smithery package as dependency - Create smithery_server.py with @smithery.server() decorator - Add SmitheryConfigSchema for session config (nextcloud_url, username, app_password) - Add [tool.smithery] section to pyproject.toml - Remove manual .well-known/mcp-config endpoint (Smithery handles this) Smithery CLI will automatically: - Extract config schema from the decorated function - Handle session config parsing from query parameters - Make config accessible via ctx.session_config in tools 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1407,51 +1407,6 @@ def get_app(transport: str = "sse", enabled_apps: list[str] | None = None):
|
||||
routes.append(Route("/health/ready", health_ready, methods=["GET"]))
|
||||
logger.info("Health check endpoints enabled: /health/live, /health/ready")
|
||||
|
||||
# ADR-016: MCP config discovery endpoint for Smithery
|
||||
# Returns the session configuration schema that Smithery uses to render the config UI
|
||||
def mcp_config(request):
|
||||
"""MCP configuration discovery endpoint.
|
||||
|
||||
Returns the JSON schema for session configuration parameters.
|
||||
Used by Smithery to render the configuration form for users.
|
||||
"""
|
||||
return JSONResponse(
|
||||
{
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"required": ["nextcloud_url", "username", "app_password"],
|
||||
"properties": {
|
||||
"nextcloud_url": {
|
||||
"type": "string",
|
||||
"title": "Nextcloud URL",
|
||||
"description": "Your Nextcloud instance URL (e.g., https://cloud.example.com). Must be publicly accessible.",
|
||||
"pattern": "^https?://.+",
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"title": "Username",
|
||||
"description": "Your Nextcloud username",
|
||||
"minLength": 1,
|
||||
},
|
||||
"app_password": {
|
||||
"type": "string",
|
||||
"title": "App Password",
|
||||
"description": "Nextcloud app password. Generate at Settings > Security > App passwords. Do NOT use your main password.",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"exampleConfig": {
|
||||
"nextcloud_url": "https://cloud.example.com",
|
||||
"username": "alice",
|
||||
"app_password": "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
routes.append(Route("/.well-known/mcp-config", mcp_config, methods=["GET"]))
|
||||
logger.info("MCP config discovery endpoint enabled: /.well-known/mcp-config")
|
||||
|
||||
# Add test webhook endpoint (for development/testing)
|
||||
routes.append(
|
||||
Route("/webhooks/nextcloud", handle_nextcloud_webhook, methods=["POST"])
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Smithery server factory for stateless deployment.
|
||||
|
||||
ADR-016: This module provides a server factory function decorated with
|
||||
@smithery.server() for Smithery CLI deployment. Session configuration
|
||||
is automatically handled by Smithery and accessible via ctx.session_config.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from pydantic import BaseModel, Field
|
||||
from smithery.decorators import smithery
|
||||
|
||||
from nextcloud_mcp_server.server import (
|
||||
configure_calendar_tools,
|
||||
configure_contacts_tools,
|
||||
configure_cookbook_tools,
|
||||
configure_deck_tools,
|
||||
configure_notes_tools,
|
||||
configure_sharing_tools,
|
||||
configure_tables_tools,
|
||||
configure_webdav_tools,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SmitheryConfigSchema(BaseModel):
|
||||
"""Configuration schema for Smithery session.
|
||||
|
||||
These fields are collected by Smithery's configuration UI and passed
|
||||
to the server with each request as session_config.
|
||||
"""
|
||||
|
||||
nextcloud_url: str = Field(
|
||||
...,
|
||||
description="Your Nextcloud instance URL (e.g., https://cloud.example.com)",
|
||||
)
|
||||
username: str = Field(
|
||||
...,
|
||||
description="Your Nextcloud username",
|
||||
)
|
||||
app_password: str = Field(
|
||||
...,
|
||||
description="Nextcloud app password (Settings > Security > App passwords)",
|
||||
)
|
||||
|
||||
|
||||
@smithery.server(config_schema=SmitheryConfigSchema)
|
||||
def create_server():
|
||||
"""Create and return a FastMCP server instance for Smithery deployment.
|
||||
|
||||
This function is called by Smithery CLI to create the server.
|
||||
Session configuration is automatically handled by Smithery and
|
||||
accessible via ctx.session_config in tool handlers.
|
||||
"""
|
||||
# Force Smithery mode
|
||||
os.environ["SMITHERY_DEPLOYMENT"] = "true"
|
||||
os.environ["VECTOR_SYNC_ENABLED"] = "false"
|
||||
|
||||
logger.info("Creating Nextcloud MCP Server for Smithery deployment")
|
||||
|
||||
# Import lifespan after setting env vars
|
||||
from nextcloud_mcp_server.app import app_lifespan_smithery
|
||||
|
||||
# Create FastMCP server with Smithery lifespan
|
||||
mcp = FastMCP("Nextcloud MCP", lifespan=app_lifespan_smithery)
|
||||
|
||||
# Register all core tools (semantic search is skipped in Smithery mode)
|
||||
configure_notes_tools(mcp)
|
||||
configure_tables_tools(mcp)
|
||||
configure_webdav_tools(mcp)
|
||||
configure_sharing_tools(mcp)
|
||||
configure_calendar_tools(mcp)
|
||||
configure_contacts_tools(mcp)
|
||||
configure_cookbook_tools(mcp)
|
||||
configure_deck_tools(mcp)
|
||||
|
||||
logger.info("Smithery server configured with core Nextcloud tools")
|
||||
|
||||
return mcp
|
||||
@@ -39,6 +39,7 @@ dependencies = [
|
||||
"pymupdf>=1.26.6",
|
||||
"pymupdf4llm>=0.2.2",
|
||||
"pymupdf-layout>=1.26.6",
|
||||
"smithery>=0.4.4",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
@@ -132,3 +133,6 @@ name = "testpypi"
|
||||
url = "https://test.pypi.org/simple/"
|
||||
publish-url = "https://test.pypi.org/legacy/"
|
||||
explicit = true
|
||||
|
||||
[tool.smithery]
|
||||
server = "nextcloud_mcp_server.smithery_server:create_server"
|
||||
|
||||
@@ -195,6 +195,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "art"
|
||||
version = "6.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/7d/7d80509bbd19fb747edef94ba487dbadd2747944774ccc0528ad0d005a36/art-6.5.tar.gz", hash = "sha256:a98d77b42c278697ec6cf4b5bdcdfd997f6b2425332da078d4e31e31377d1844", size = 672902, upload-time = "2025-04-12T17:02:20.279Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/29/57b06fdb3abdf52c621d3ca3caea735e2db4c8d48288ebd26af448e8e247/art-6.5-py3-none-any.whl", hash = "sha256:70706408144c45c666caab690627d5c74aea7b6c7ce8cc968408ddeef8d84afd", size = 610382, upload-time = "2025-04-12T17:02:21.97Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.10.0"
|
||||
@@ -1967,6 +1976,7 @@ dependencies = [
|
||||
{ name = "python-json-logger" },
|
||||
{ name = "pythonvcard4" },
|
||||
{ name = "qdrant-client" },
|
||||
{ name = "smithery" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
@@ -2015,6 +2025,7 @@ requires-dist = [
|
||||
{ name = "python-json-logger", specifier = ">=3.2.0" },
|
||||
{ name = "pythonvcard4", specifier = ">=0.2.0" },
|
||||
{ name = "qdrant-client", specifier = ">=1.7.0" },
|
||||
{ name = "smithery", specifier = ">=0.4.4" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
@@ -3554,6 +3565,23 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithery"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "art" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "toml" },
|
||||
{ name = "typer" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/75/d0b0fc1a5c10a20f3e01cefd98276ccbe7b44d74eeb6551bd2f42d8b4768/smithery-0.4.4.tar.gz", hash = "sha256:18ae19af8405e6476ca4984036d4460822ec1647ad2262addb4909d03387d671", size = 17396, upload-time = "2025-10-24T15:47:44.543Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/54/a088fa621c9c76a72a70079fac42b822137a1609ec08525168bd8e9f415d/smithery-0.4.4-py3-none-any.whl", hash = "sha256:883d060b3ecc73a2972019760e342f5a04b62edf18e3bc03594a41851319808a", size = 25372, upload-time = "2025-10-24T15:47:43.045Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
@@ -3675,6 +3703,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684, upload-time = "2025-09-19T09:49:24.953Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.3.0"
|
||||
|
||||
Reference in New Issue
Block a user