feat(server): Experimental support for OAuth2/OIDC authentication

This commit is contained in:
Chris Coutinho
2025-10-13 18:07:46 +02:00
parent fafede2282
commit 4d7e4b9a4b
23 changed files with 2767 additions and 97 deletions
+12 -12
View File
@@ -4,7 +4,7 @@ from typing import Optional
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.calendar import (
Calendar,
ListCalendarsResponse,
@@ -18,7 +18,7 @@ def configure_calendar_tools(mcp: FastMCP):
@mcp.tool()
async def nc_calendar_list_calendars(ctx: Context) -> ListCalendarsResponse:
"""List all available calendars for the user"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
calendars_data = await client.calendar.list_calendars()
calendars = [Calendar(**cal_data) for cal_data in calendars_data]
@@ -74,7 +74,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
Dict with event creation result
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
event_data = {
"title": title,
@@ -133,7 +133,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
List of events matching the filters
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Convert YYYY-MM-DD format dates to datetime objects
start_datetime = None
@@ -207,7 +207,7 @@ def configure_calendar_tools(mcp: FastMCP):
ctx: Context,
):
"""Get detailed information about a specific event"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
event_data, etag = await client.calendar.get_event(calendar_name, event_uid)
return event_data
@@ -240,7 +240,7 @@ def configure_calendar_tools(mcp: FastMCP):
etag: str = "",
):
"""Update any aspect of an existing event"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Build update data with only non-None values
event_data = {}
@@ -290,7 +290,7 @@ def configure_calendar_tools(mcp: FastMCP):
ctx: Context,
):
"""Delete a calendar event"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.calendar.delete_event(calendar_name, event_uid)
@mcp.tool()
@@ -332,7 +332,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
Dict with meeting creation result
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Combine date and time for start_datetime
start_datetime = f"{date}T{time}:00"
@@ -366,7 +366,7 @@ def configure_calendar_tools(mcp: FastMCP):
limit: int = 10,
):
"""Get upcoming events in next N days"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
now = dt.datetime.now()
end_datetime = now + dt.timedelta(days=days_ahead)
@@ -435,7 +435,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
List of available time slots with start/end times and duration
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Parse attendees
attendee_list = []
@@ -536,7 +536,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
Summary of operation results including counts and details
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
if operation not in ["update", "delete", "move"]:
raise ValueError("Operation must be 'update', 'delete', or 'move'")
@@ -758,7 +758,7 @@ def configure_calendar_tools(mcp: FastMCP):
Returns:
Result of the calendar management operation
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
if action == "list":
return await client.calendar.list_calendars()
+8 -8
View File
@@ -2,7 +2,7 @@ import logging
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
logger = logging.getLogger(__name__)
@@ -12,13 +12,13 @@ def configure_contacts_tools(mcp: FastMCP):
@mcp.tool()
async def nc_contacts_list_addressbooks(ctx: Context):
"""List all addressbooks for the user."""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.list_addressbooks()
@mcp.tool()
async def nc_contacts_list_contacts(ctx: Context, *, addressbook: str):
"""List all contacts in the specified addressbook."""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.list_contacts(addressbook=addressbook)
@mcp.tool()
@@ -31,7 +31,7 @@ def configure_contacts_tools(mcp: FastMCP):
name: The name of the addressbook.
display_name: The display name of the addressbook.
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.create_addressbook(
name=name, display_name=display_name
)
@@ -39,7 +39,7 @@ def configure_contacts_tools(mcp: FastMCP):
@mcp.tool()
async def nc_contacts_delete_addressbook(ctx: Context, *, name: str):
"""Delete an addressbook."""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.delete_addressbook(name=name)
@mcp.tool()
@@ -53,7 +53,7 @@ def configure_contacts_tools(mcp: FastMCP):
uid: The unique ID for the contact.
contact_data: A dictionary with the contact's details, e.g. {"fn": "John Doe", "email": "john.doe@example.com"}.
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.create_contact(
addressbook=addressbook, uid=uid, contact_data=contact_data
)
@@ -61,7 +61,7 @@ def configure_contacts_tools(mcp: FastMCP):
@mcp.tool()
async def nc_contacts_delete_contact(ctx: Context, *, addressbook: str, uid: str):
"""Delete a contact."""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.delete_contact(addressbook=addressbook, uid=uid)
@mcp.tool()
@@ -76,7 +76,7 @@ def configure_contacts_tools(mcp: FastMCP):
contact_data: A dictionary with the contact's updated details, e.g. {"fn": "Jane Doe", "email": "jane.doe@example.com"}.
etag: Optional ETag for optimistic concurrency control.
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.contacts.update_contact(
addressbook=addressbook, uid=uid, contact_data=contact_data, etag=etag
)
+34 -34
View File
@@ -3,7 +3,7 @@ from typing import Optional
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.deck import (
DeckBoard,
DeckStack,
@@ -30,7 +30,7 @@ def configure_deck_tools(mcp: FastMCP):
"""List all Nextcloud Deck boards"""
ctx: Context = mcp.get_context()
await ctx.warning("This message is deprecated, use the deck_get_board instead")
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
boards = await client.deck.get_boards()
return [board.model_dump() for board in boards]
@@ -41,7 +41,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_board tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
board = await client.deck.get_board(board_id)
return board.model_dump()
@@ -52,7 +52,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_stacks tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stacks = await client.deck.get_stacks(board_id)
return [stack.model_dump() for stack in stacks]
@@ -63,7 +63,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_stack tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stack = await client.deck.get_stack(board_id, stack_id)
return stack.model_dump()
@@ -74,7 +74,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_cards tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stack = await client.deck.get_stack(board_id, stack_id)
if stack.cards:
return [card.model_dump() for card in stack.cards]
@@ -87,7 +87,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_card tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
card = await client.deck.get_card(board_id, stack_id, card_id)
return card.model_dump()
@@ -98,7 +98,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_labels tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
board = await client.deck.get_board(board_id)
return [label.model_dump() for label in board.labels]
@@ -109,7 +109,7 @@ def configure_deck_tools(mcp: FastMCP):
await ctx.warning(
"This resource is deprecated, use the deck_get_label tool instead"
)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
label = await client.deck.get_label(board_id, label_id)
return label.model_dump()
@@ -118,28 +118,28 @@ def configure_deck_tools(mcp: FastMCP):
@mcp.tool()
async def deck_get_boards(ctx: Context) -> list[DeckBoard]:
"""Get all Nextcloud Deck boards"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
boards = await client.deck.get_boards()
return boards
@mcp.tool()
async def deck_get_board(ctx: Context, board_id: int) -> DeckBoard:
"""Get details of a specific Nextcloud Deck board"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
board = await client.deck.get_board(board_id)
return board
@mcp.tool()
async def deck_get_stacks(ctx: Context, board_id: int) -> list[DeckStack]:
"""Get all stacks in a Nextcloud Deck board"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stacks = await client.deck.get_stacks(board_id)
return stacks
@mcp.tool()
async def deck_get_stack(ctx: Context, board_id: int, stack_id: int) -> DeckStack:
"""Get details of a specific Nextcloud Deck stack"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stack = await client.deck.get_stack(board_id, stack_id)
return stack
@@ -148,7 +148,7 @@ def configure_deck_tools(mcp: FastMCP):
ctx: Context, board_id: int, stack_id: int
) -> list[DeckCard]:
"""Get all cards in a Nextcloud Deck stack"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stack = await client.deck.get_stack(board_id, stack_id)
if stack.cards:
return stack.cards
@@ -159,21 +159,21 @@ def configure_deck_tools(mcp: FastMCP):
ctx: Context, board_id: int, stack_id: int, card_id: int
) -> DeckCard:
"""Get details of a specific Nextcloud Deck card"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
card = await client.deck.get_card(board_id, stack_id, card_id)
return card
@mcp.tool()
async def deck_get_labels(ctx: Context, board_id: int) -> list[DeckLabel]:
"""Get all labels in a Nextcloud Deck board"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
board = await client.deck.get_board(board_id)
return board.labels
@mcp.tool()
async def deck_get_label(ctx: Context, board_id: int, label_id: int) -> DeckLabel:
"""Get details of a specific Nextcloud Deck label"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
label = await client.deck.get_label(board_id, label_id)
return label
@@ -189,7 +189,7 @@ def configure_deck_tools(mcp: FastMCP):
title: The title of the new board
color: The hexadecimal color of the new board (e.g. FF0000)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
board = await client.deck.create_board(title, color)
return CreateBoardResponse(id=board.id, title=board.title, color=board.color)
@@ -206,7 +206,7 @@ def configure_deck_tools(mcp: FastMCP):
title: The title of the new stack
order: Order for sorting the stacks
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
stack = await client.deck.create_stack(board_id, title, order)
return CreateStackResponse(id=stack.id, title=stack.title, order=stack.order)
@@ -226,7 +226,7 @@ def configure_deck_tools(mcp: FastMCP):
title: New title for the stack
order: New order for the stack
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.update_stack(board_id, stack_id, title, order)
return StackOperationResponse(
success=True,
@@ -245,7 +245,7 @@ def configure_deck_tools(mcp: FastMCP):
board_id: The ID of the board
stack_id: The ID of the stack
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.delete_stack(board_id, stack_id)
return StackOperationResponse(
success=True,
@@ -277,7 +277,7 @@ def configure_deck_tools(mcp: FastMCP):
description: Description of the card
duedate: Due date of the card (ISO-8601 format)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
card = await client.deck.create_card(
board_id, stack_id, title, type, order, description, duedate
)
@@ -318,7 +318,7 @@ def configure_deck_tools(mcp: FastMCP):
archived: Whether the card should be archived
done: Completion date for the card (ISO-8601 format)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.update_card(
board_id,
stack_id,
@@ -351,7 +351,7 @@ def configure_deck_tools(mcp: FastMCP):
stack_id: The ID of the stack
card_id: The ID of the card
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.delete_card(board_id, stack_id, card_id)
return CardOperationResponse(
success=True,
@@ -372,7 +372,7 @@ def configure_deck_tools(mcp: FastMCP):
stack_id: The ID of the stack
card_id: The ID of the card
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.archive_card(board_id, stack_id, card_id)
return CardOperationResponse(
success=True,
@@ -393,7 +393,7 @@ def configure_deck_tools(mcp: FastMCP):
stack_id: The ID of the stack
card_id: The ID of the card
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.unarchive_card(board_id, stack_id, card_id)
return CardOperationResponse(
success=True,
@@ -421,7 +421,7 @@ def configure_deck_tools(mcp: FastMCP):
order: New position in the target stack
target_stack_id: The ID of the target stack
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.reorder_card(
board_id, stack_id, card_id, order, target_stack_id
)
@@ -445,7 +445,7 @@ def configure_deck_tools(mcp: FastMCP):
title: The title of the new label
color: The color of the new label (hex format without #)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
label = await client.deck.create_label(board_id, title, color)
return CreateLabelResponse(id=label.id, title=label.title, color=label.color)
@@ -465,7 +465,7 @@ def configure_deck_tools(mcp: FastMCP):
title: New title for the label
color: New color for the label (hex format without #)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.update_label(board_id, label_id, title, color)
return LabelOperationResponse(
success=True,
@@ -484,7 +484,7 @@ def configure_deck_tools(mcp: FastMCP):
board_id: The ID of the board
label_id: The ID of the label
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.delete_label(board_id, label_id)
return LabelOperationResponse(
success=True,
@@ -506,7 +506,7 @@ def configure_deck_tools(mcp: FastMCP):
card_id: The ID of the card
label_id: The ID of the label to assign
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.assign_label_to_card(board_id, stack_id, card_id, label_id)
return CardOperationResponse(
success=True,
@@ -528,7 +528,7 @@ def configure_deck_tools(mcp: FastMCP):
card_id: The ID of the card
label_id: The ID of the label to remove
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.remove_label_from_card(board_id, stack_id, card_id, label_id)
return CardOperationResponse(
success=True,
@@ -551,7 +551,7 @@ def configure_deck_tools(mcp: FastMCP):
card_id: The ID of the card
user_id: The user ID to assign
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.assign_user_to_card(board_id, stack_id, card_id, user_id)
return CardOperationResponse(
success=True,
@@ -573,7 +573,7 @@ def configure_deck_tools(mcp: FastMCP):
card_id: The ID of the card
user_id: The user ID to unassign
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
await client.deck.unassign_user_from_card(board_id, stack_id, card_id, user_id)
return CardOperationResponse(
success=True,
+11 -11
View File
@@ -5,7 +5,7 @@ from mcp.types import ErrorData
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.notes import (
Note,
NotesSettings,
@@ -27,7 +27,7 @@ def configure_notes_tools(mcp: FastMCP):
ctx: Context = (
mcp.get_context()
) # https://github.com/modelcontextprotocol/python-sdk/issues/244
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
settings_data = await client.notes.get_settings()
return NotesSettings(**settings_data)
@@ -35,7 +35,7 @@ def configure_notes_tools(mcp: FastMCP):
async def nc_notes_get_attachment_resource(note_id: int, attachment_filename: str):
"""Get a specific attachment from a note"""
ctx: Context = mcp.get_context()
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Assuming a method get_note_attachment exists in the client
# This method should return the raw content and determine the mime type
content, mime_type = await client.webdav.get_note_attachment(
@@ -57,7 +57,7 @@ def configure_notes_tools(mcp: FastMCP):
"""Get user note using note id"""
ctx: Context = mcp.get_context()
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
note_data = await client.notes.get_note(note_id)
return Note(**note_data)
@@ -81,7 +81,7 @@ def configure_notes_tools(mcp: FastMCP):
title: str, content: str, category: str, ctx: Context
) -> CreateNoteResponse:
"""Create a new note"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
note_data = await client.notes.create_note(
title=title,
@@ -133,7 +133,7 @@ def configure_notes_tools(mcp: FastMCP):
If the note has been modified by someone else since you retrieved it,
the update will fail with a 412 error."""
logger.info("Updating note %s", note_id)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
note_data = await client.notes.update(
note_id=note_id,
@@ -183,7 +183,7 @@ def configure_notes_tools(mcp: FastMCP):
between the note and what will be appended."""
logger.info("Appending content to note %s", note_id)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
note_data = await client.notes.append_content(
note_id=note_id, content=content
@@ -220,7 +220,7 @@ def configure_notes_tools(mcp: FastMCP):
@mcp.tool()
async def nc_notes_search_notes(query: str, ctx: Context) -> SearchNotesResponse:
"""Search notes by title or content, returning only id, title, and category."""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
search_results_raw = await client.notes_search_notes(query=query)
@@ -261,7 +261,7 @@ def configure_notes_tools(mcp: FastMCP):
@mcp.tool()
async def nc_notes_get_note(note_id: int, ctx: Context) -> Note:
"""Get a specific note by its ID"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
note_data = await client.notes.get_note(note_id)
return Note(**note_data)
@@ -285,7 +285,7 @@ def configure_notes_tools(mcp: FastMCP):
note_id: int, attachment_filename: str, ctx: Context
) -> dict[str, str]:
"""Get a specific attachment from a note"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
content, mime_type = await client.webdav.get_note_attachment(
note_id=note_id, filename=attachment_filename
@@ -322,7 +322,7 @@ def configure_notes_tools(mcp: FastMCP):
async def nc_notes_delete_note(note_id: int, ctx: Context) -> DeleteNoteResponse:
"""Delete a note permanently"""
logger.info("Deleting note %s", note_id)
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
try:
await client.notes.delete_note(note_id)
return DeleteNoteResponse(
+7 -7
View File
@@ -2,7 +2,7 @@ import logging
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
logger = logging.getLogger(__name__)
@@ -12,13 +12,13 @@ def configure_tables_tools(mcp: FastMCP):
@mcp.tool()
async def nc_tables_list_tables(ctx: Context):
"""List all tables available to the user"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.list_tables()
@mcp.tool()
async def nc_tables_get_schema(table_id: int, ctx: Context):
"""Get the schema/structure of a specific table including columns and views"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.get_table_schema(table_id)
@mcp.tool()
@@ -29,7 +29,7 @@ def configure_tables_tools(mcp: FastMCP):
offset: int | None = None,
):
"""Read rows from a table with optional pagination"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.get_table_rows(table_id, limit, offset)
@mcp.tool()
@@ -38,7 +38,7 @@ def configure_tables_tools(mcp: FastMCP):
Data should be a dictionary mapping column IDs to values, e.g. {1: "text", 2: 42}
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.create_row(table_id, data)
@mcp.tool()
@@ -47,11 +47,11 @@ def configure_tables_tools(mcp: FastMCP):
Data should be a dictionary mapping column IDs to new values, e.g. {1: "new text", 2: 99}
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.update_row(row_id, data)
@mcp.tool()
async def nc_tables_delete_row(row_id: int, ctx: Context):
"""Delete a row from a table"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.tables.delete_row(row_id)
+8 -8
View File
@@ -2,7 +2,7 @@ import logging
from mcp.server.fastmcp import Context, FastMCP
from nextcloud_mcp_server.client import NextcloudClient
from nextcloud_mcp_server.context import get_client
logger = logging.getLogger(__name__)
@@ -26,7 +26,7 @@ def configure_webdav_tools(mcp: FastMCP):
# List a specific folder
await nc_webdav_list_directory("Documents/Projects")
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.webdav.list_directory(path)
@mcp.tool()
@@ -49,7 +49,7 @@ def configure_webdav_tools(mcp: FastMCP):
result = await nc_webdav_read_file("Images/photo.jpg")
logger.info(result['encoding']) # 'base64'
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
content, content_type = await client.webdav.read_file(path)
# For text files, decode content for easier viewing
@@ -97,7 +97,7 @@ def configure_webdav_tools(mcp: FastMCP):
# Write binary data (base64 encoded)
await nc_webdav_write_file("files/data.bin", base64_content, "application/octet-stream;base64")
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
# Handle base64 encoded content
if content_type and "base64" in content_type.lower():
@@ -127,7 +127,7 @@ def configure_webdav_tools(mcp: FastMCP):
# Create nested directories (parent must exist)
await nc_webdav_create_directory("Projects/MyApp/docs")
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.webdav.create_directory(path)
@mcp.tool()
@@ -147,7 +147,7 @@ def configure_webdav_tools(mcp: FastMCP):
# Delete a directory (will delete all contents)
await nc_webdav_delete_resource("temp_folder")
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.webdav.delete_resource(path)
@mcp.tool()
@@ -177,7 +177,7 @@ def configure_webdav_tools(mcp: FastMCP):
# Move and overwrite if destination exists
await nc_webdav_move_resource("document.txt", "Archive/document.txt", overwrite=True)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.webdav.move_resource(
source_path, destination_path, overwrite
)
@@ -209,7 +209,7 @@ def configure_webdav_tools(mcp: FastMCP):
# Copy and overwrite if destination exists
await nc_webdav_copy_resource("document.txt", "Backup/document.txt", overwrite=True)
"""
client: NextcloudClient = ctx.request_context.lifespan_context.client
client = get_client(ctx)
return await client.webdav.copy_resource(
source_path, destination_path, overwrite
)