Skip to content

Commit 0f72460

Browse files
authored
Merge pull request #10 from jkawamoto/fix-tools
Refactor server setup and ignore type error
2 parents 3be6e4d + 710ef6e commit 0f72460

File tree

1 file changed

+53
-52
lines changed

1 file changed

+53
-52
lines changed

src/mcp_bear/server.py

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@
1313
from contextlib import asynccontextmanager
1414
from copy import deepcopy
1515
from dataclasses import dataclass
16+
from functools import partial
1617
from http import HTTPStatus
1718
from typing import cast, AsyncIterator
1819
from urllib.parse import urlencode, quote, unquote_plus
1920

2021
from fastapi import FastAPI, Request
2122
from mcp.server import FastMCP
2223
from mcp.server.fastmcp import Context
23-
from mcp.server.session import ServerSessionT
2424
from pydantic import Field
2525
from starlette.datastructures import QueryParams
2626
from uvicorn import Config, Server
2727
from uvicorn.config import LOGGING_CONFIG
2828

2929
BASE_URL = "bear://x-callback-url"
3030

31+
LOGGER = logging.getLogger(__name__)
32+
3133

3234
@dataclass
3335
class ErrorResponse(Exception):
@@ -79,53 +81,52 @@ class AppContext:
7981
grab_url_results: Queue[Future[QueryParams]]
8082

8183

82-
def create_server(token: str, callback_host: str, callback_port: int) -> FastMCP:
83-
logger = logging.getLogger(__name__)
84-
85-
@asynccontextmanager
86-
async def app_lifespan(_server: FastMCP) -> AsyncIterator[AppContext]:
87-
callback = FastAPI()
88-
89-
log_config = deepcopy(LOGGING_CONFIG)
90-
log_config["handlers"]["access"]["stream"] = "ext://sys.stderr"
91-
server = Server(
92-
Config(
93-
app=callback,
94-
host=callback_host,
95-
port=callback_port,
96-
log_level="warning",
97-
log_config=log_config,
98-
)
84+
@asynccontextmanager
85+
async def app_lifespan(_server: FastMCP, callback_host: str, callback_port: int) -> AsyncIterator[AppContext]:
86+
callback = FastAPI()
87+
88+
log_config = deepcopy(LOGGING_CONFIG)
89+
log_config["handlers"]["access"]["stream"] = "ext://sys.stderr"
90+
server = Server(
91+
Config(
92+
app=callback,
93+
host=callback_host,
94+
port=callback_port,
95+
log_level="warning",
96+
log_config=log_config,
9997
)
98+
)
99+
100+
LOGGER.info(f"Starting callback server on {callback_host}:{callback_port}")
101+
server_task = asyncio.create_task(server.serve())
102+
try:
103+
yield AppContext(
104+
open_note_results=register_callback(callback, "open-note"),
105+
create_results=register_callback(callback, "create"),
106+
tags_results=register_callback(callback, "tags"),
107+
open_tag_results=register_callback(callback, "open-tag"),
108+
todo_results=register_callback(callback, "todo"),
109+
today_results=register_callback(callback, "today"),
110+
search_results=register_callback(callback, "search"),
111+
grab_url_results=register_callback(callback, "grab-url"),
112+
)
113+
finally:
114+
LOGGER.info("Stopping callback server")
115+
server.should_exit = True
116+
await server_task
100117

101-
logger.info(f"Starting callback server on {callback_host}:{callback_port}")
102-
server_task = asyncio.create_task(server.serve())
103-
try:
104-
yield AppContext(
105-
open_note_results=register_callback(callback, "open-note"),
106-
create_results=register_callback(callback, "create"),
107-
tags_results=register_callback(callback, "tags"),
108-
open_tag_results=register_callback(callback, "open-tag"),
109-
todo_results=register_callback(callback, "todo"),
110-
today_results=register_callback(callback, "today"),
111-
search_results=register_callback(callback, "search"),
112-
grab_url_results=register_callback(callback, "grab-url"),
113-
)
114-
finally:
115-
logger.info("Stopping callback server")
116-
server.should_exit = True
117-
await server_task
118118

119-
mcp = FastMCP("Bear", lifespan=app_lifespan)
119+
def create_server(token: str, callback_host: str, callback_port: int) -> FastMCP:
120+
mcp = FastMCP("Bear", lifespan=partial(app_lifespan, callback_host=callback_host, callback_port=callback_port))
120121

121122
@mcp.tool()
122123
async def open_note(
123-
ctx: Context[ServerSessionT, AppContext],
124+
ctx: Context,
124125
id: str | None = Field(description="note unique identifier", default=None),
125126
title: str | None = Field(description="note title", default=None),
126127
) -> str:
127128
"""Open a note identified by its title or id and return its content."""
128-
app_ctx = ctx.request_context.lifespan_context
129+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
129130
future = Future[QueryParams]()
130131
await app_ctx.open_note_results.put(future)
131132

@@ -152,14 +153,14 @@ async def open_note(
152153

153154
@mcp.tool()
154155
async def create(
155-
ctx: Context[ServerSessionT, AppContext],
156+
ctx: Context,
156157
title: str | None = Field(description="note title", default=None),
157158
text: str | None = Field(description="note body", default=None),
158159
tags: list[str] | None = Field(description="list of tags", default=None),
159160
timestamp: bool = Field(description="prepend the current date and time to the text", default=False),
160161
) -> str:
161162
"""Create a new note and return its unique identifier. Empty notes are not allowed."""
162-
app_ctx = ctx.request_context.lifespan_context
163+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
163164
future = Future[QueryParams]()
164165
await app_ctx.create_results.put(future)
165166

@@ -187,10 +188,10 @@ async def create(
187188

188189
@mcp.tool()
189190
async def tags(
190-
ctx: Context[ServerSessionT, AppContext],
191+
ctx: Context,
191192
) -> list[str]:
192193
"""Return all the tags currently displayed in Bear’s sidebar."""
193-
app_ctx = ctx.request_context.lifespan_context
194+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
194195
future = Future[QueryParams]()
195196
await app_ctx.tags_results.put(future)
196197

@@ -208,11 +209,11 @@ async def tags(
208209

209210
@mcp.tool()
210211
async def open_tag(
211-
ctx: Context[ServerSessionT, AppContext],
212+
ctx: Context,
212213
name: str = Field(description="tag name or a list of tags divided by comma"),
213214
) -> list[str]:
214215
"""Show all the notes which have a selected tag in bear."""
215-
app_ctx = ctx.request_context.lifespan_context
216+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
216217
future = Future[QueryParams]()
217218
await app_ctx.open_tag_results.put(future)
218219

@@ -231,11 +232,11 @@ async def open_tag(
231232

232233
@mcp.tool()
233234
async def todo(
234-
ctx: Context[ServerSessionT, AppContext],
235+
ctx: Context,
235236
search: str | None = Field(description="string to search", default=None),
236237
) -> list[str]:
237238
"""Select the Todo sidebar item."""
238-
app_ctx = ctx.request_context.lifespan_context
239+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
239240
future = Future[QueryParams]()
240241
await app_ctx.todo_results.put(future)
241242

@@ -256,11 +257,11 @@ async def todo(
256257

257258
@mcp.tool()
258259
async def today(
259-
ctx: Context[ServerSessionT, AppContext],
260+
ctx: Context,
260261
search: str | None = Field(description="string to search", default=None),
261262
) -> list[str]:
262263
"""Select the Today sidebar item."""
263-
app_ctx = ctx.request_context.lifespan_context
264+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
264265
future = Future[QueryParams]()
265266
await app_ctx.today_results.put(future)
266267

@@ -281,12 +282,12 @@ async def today(
281282

282283
@mcp.tool()
283284
async def search(
284-
ctx: Context[ServerSessionT, AppContext],
285+
ctx: Context,
285286
term: str | None = Field(description="string to search", default=None),
286287
tag: str | None = Field(description="tag to search into", default=None),
287288
) -> list[str]:
288289
"""Show search results in Bear for all notes or for a specific tag."""
289-
app_ctx = ctx.request_context.lifespan_context
290+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
290291
future = Future[QueryParams]()
291292
await app_ctx.search_results.put(future)
292293

@@ -309,15 +310,15 @@ async def search(
309310

310311
@mcp.tool()
311312
async def grab_url(
312-
ctx: Context[ServerSessionT, AppContext],
313+
ctx: Context,
313314
url: str = Field(description="url to grab"),
314315
tags: list[str] | None = Field(
315316
description="list of tags. If tags are specified in the Bear’s web content preferences, this parameter is ignored.",
316317
default=None,
317318
),
318319
) -> str:
319320
"""Create a new note with the content of a web page and return its unique identifier."""
320-
app_ctx = ctx.request_context.lifespan_context
321+
app_ctx: AppContext = ctx.request_context.lifespan_context # type: ignore
321322
future = Future[QueryParams]()
322323
await app_ctx.grab_url_results.put(future)
323324

0 commit comments

Comments
 (0)