Skip to content

Commit b69cc9b

Browse files
authored
Merge pull request #41 from jkawamoto/add-title
Add `add_title` tool with tests
2 parents 8b246a0 + a4278e5 commit b69cc9b

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Refer to Bear's [X-callback-url Scheme documentation](https://bear.app/faq/x-cal
8181

8282
- [x] /open-note
8383
- [x] /create
84-
- [x] /add-text (partially, via the replace_note method)
84+
- [x] /add-text (partially, via the replace_note and add_title method)
8585
- [x] /add-file
8686
- [x] /tags
8787
- [x] /open-tag

src/mcp_bear/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,26 @@ async def replace_note(
243243

244244
return ModifiedNote.model_validate(await _request(ctx, "add-text", params))
245245

246+
@mcp.tool()
247+
async def add_title(
248+
ctx: Context[Any, AppContext],
249+
id: str = Field(description="note unique identifier"),
250+
title: str = Field(description="new title for the note"),
251+
) -> None:
252+
"""Add a title to a note identified by its id."""
253+
if not title.startswith("# "):
254+
title = "# " + title
255+
params = {
256+
"id": id,
257+
"text": title,
258+
"mode": "prepend",
259+
"open_note": "no",
260+
"new_window": "no",
261+
"show_window": "no",
262+
"edit": "no",
263+
}
264+
await _request(ctx, "add-text", params)
265+
246266
@mcp.tool()
247267
async def add_file(
248268
ctx: Context[Any, AppContext],

tests/test_server.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,53 @@ async def test_replace_note_failed(
274274
assert len(ctx.request_context.lifespan_context.futures) == 0
275275

276276

277+
@pytest.mark.anyio
278+
@pytest.mark.parametrize(
279+
"argument,expect",
280+
[
281+
("title", "# title"),
282+
("# title", "# title"),
283+
],
284+
)
285+
async def test_add_title(
286+
temp_socket: Path,
287+
mcp_server: Tuple[FastMCP, Context],
288+
mock_webbrowser: MagicMock,
289+
argument: str,
290+
expect: str,
291+
) -> None:
292+
s, ctx = mcp_server
293+
mock_webbrowser.stubbed_queries = {}
294+
295+
await s._tool_manager.call_tool("add_title", arguments={"id": "123", "title": argument}, context=ctx)
296+
assert len(ctx.request_context.lifespan_context.futures) == 0
297+
298+
req_params = {
299+
"id": "123",
300+
"text": expect,
301+
"mode": "prepend",
302+
"open_note": "no",
303+
"new_window": "no",
304+
"show_window": "no",
305+
"edit": "no",
306+
"x-success": f"xfwder://{temp_socket.stem}/{ctx.request_id}/success",
307+
"x-error": f"xfwder://{temp_socket.stem}/{ctx.request_id}/error",
308+
}
309+
mock_webbrowser.assert_called_once_with(f"{BASE_URL}/add-text?{urlencode(req_params, quote_via=quote)}")
310+
311+
312+
@pytest.mark.anyio
313+
async def test_add_title_failed(
314+
mcp_server: Tuple[FastMCP, Context[Any, AppContext]], mock_webbrowser_error: MagicMock
315+
) -> None:
316+
s, ctx = mcp_server
317+
with pytest.raises(ToolError) as excinfo:
318+
await s._tool_manager.call_tool("add_title", arguments={"id": "123456", "title": "new title"}, context=ctx)
319+
320+
assert "test error message" in str(excinfo.value)
321+
assert len(ctx.request_context.lifespan_context.futures) == 0
322+
323+
277324
@pytest.mark.anyio
278325
@pytest.mark.parametrize(
279326
"arguments,expect_req_params",

0 commit comments

Comments
 (0)