From 5e54d48a9802ac754230a382d164859cab0d249f Mon Sep 17 00:00:00 2001 From: despiegk Date: Mon, 4 Aug 2025 10:43:14 +0200 Subject: [PATCH] ... --- env.sh | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 178 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 1704 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 1519 bytes .../__pycache__/pptx_ops.cpython-312.pyc | Bin 0 -> 8643 bytes .../__pycache__/selection.cpython-312.pyc | Bin 0 -> 3476 bytes src/docsorter/errors.py | 31 ------ src/docsorter/mcp_client.py | 78 -------------- src/docsorter/mcp_server.py | 100 ++++++++---------- src/mcptest/mcptest.py | 4 +- 10 files changed, 52 insertions(+), 164 deletions(-) create mode 100644 src/docsorter/__pycache__/__init__.cpython-312.pyc create mode 100644 src/docsorter/__pycache__/errors.cpython-312.pyc create mode 100644 src/docsorter/__pycache__/models.cpython-312.pyc create mode 100644 src/docsorter/__pycache__/pptx_ops.cpython-312.pyc create mode 100644 src/docsorter/__pycache__/selection.cpython-312.pyc delete mode 100644 src/docsorter/errors.py delete mode 100644 src/docsorter/mcp_client.py diff --git a/env.sh b/env.sh index 49532c5..21a842a 100755 --- a/env.sh +++ b/env.sh @@ -29,3 +29,6 @@ fi echo "🔄 Activating virtual environment..." source .venv/bin/activate +# Add src to PYTHONPATH +export PYTHONPATH="$SCRIPT_DIR/src:$PYTHONPATH" + diff --git a/src/docsorter/__pycache__/__init__.cpython-312.pyc b/src/docsorter/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b90431395d3d6f5d4de57f9facbd4956ab456db GIT binary patch literal 178 zcmX@j%ge<81m(3I8MZ+BF^Gc>KC=KtrZZGBXfpb(WGG?+@;-yq{0h_$Elw>e)=x<- zF33zx&(=@QPf68J&n(f)FD)w1FUm>LD@oJONG-|-OQhr{7v~q1q!#HH7bWW>c=7R> pd6^~g@p=W7w>WHa^HWN5QtgUZfi^G#aWRPTk(rT^v4|PS0st-xFD3v0 literal 0 HcmV?d00001 diff --git a/src/docsorter/__pycache__/errors.cpython-312.pyc b/src/docsorter/__pycache__/errors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4877e5baa4d37d2981f91f4b20dfc9f310f8ab77 GIT binary patch literal 1704 zcmbVML2ukd6rQoYyWV8CZK^^NT7*}q5~k`dxWe#uNeDh}feQ)0L56$Kb zfieDkyzz@h$nUr)N2PEk0-T3LBP+xwyTqrAJkj&L9#yCYsLtpCswva}v~md56xiQ9BYWrLD!@<}P841xIx z%@>r6$R6$5dRCEX%tU57P7no&;|M$jxFK2CSBtFfIEuaFNc^~E?fkvkS(8l2j?ZKq zu=P(n9`{*iJxJO-6`yet`t79O*$i%;J|Y1Ii#KTVS5=l3$HW zPiN*)a4=UUDesO1*0v|_+QL@3K%W<3xJFD<#>bpUh3f2z(K?MutQH#URBCkBvyT>be9hIQDa9GLZLdjd@ zYhXmzu4eV?LyyH+&UtsKT2wEpQlf5q`Mopq{k*$B5O2UGsuiz-^M_6;ooZFOG#*U} iOw+SEeeJ==Qv%bpq0`o*_t0-0_3Z~6e*$i+lKLMRtd~&$ literal 0 HcmV?d00001 diff --git a/src/docsorter/__pycache__/models.cpython-312.pyc b/src/docsorter/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c90a182b246916f4ca66c131282b83d47fde689c GIT binary patch literal 1519 zcmb_czi-n(6h6o2{0?m>Z7Hgd+9HIPdP730Sg2IBLIF`aki|0bwY6}3>D>kJ+=5^W z11f(2OU0kUgvwCe!ob9a)UYt|-Z>E=6j<=^`Q3NFzW2RnzpvHG1fI>;FLvMN2>FbI z;o}T~)8`Nzl8}T}Oxjk{YUi4{w%xQXk|S#*%pH)>X4X49Ti>LHrNBBPtYg?bu!Rve zZ`dNRr4hDZ*fOw{5w@6C-P#wt#I<$le%fK-YSdD?(%|YI+v`Sxg__=uq-teKG91Vv zr`KV8NEm5aA!+8e2(uXttrcKRlrxb7oNb~!L=>acar1ieu3(ZSDo_zm){||nX%e)V zrkz0T>Z**RkV(JAy9o$t^Dx?uSm+0;;TCk!_i?T7>$2~|TsOwJ>ic`$ARf%vQVBib z`$3ZM>=FrU>e# z6WmOMyd}9%On6eXyq_bG!D6}GNksupHGoFRivZ8aNx8Pap27L)V>*3OpZQ2<#D(F~ zvkgYG{$McLni-6?W(F6SGY304P;)>Pj4>ysM0- z4Yw$&sH%o=4xx@Pi7)}6=|j$AL2*2VU~-toXm$uC2;@8fBwjhUzmdVj)c&25`uv;C z5A}tQbRpY_d(KXN5oZ3Cd}_N?mraV$vrGzfTRSGLhMWI;Cgbui!(M@aX-YKVr~HGB zOfF8JE%zGCjLS9Ea0Z_QWlmPHO!O_@N$;@^w34nw33Q+`?X4Zt@{Ob4#N*#%q!we| zFW@W!@)Ao1;_K7B)~mT=dI<_J%mCN?mkg?_EM|%gawrJa=8st@mf-w`xB@ut?^Waw zu;xRFwR&&q<%457Ga?b!G~ANTtF#j(JHp0|rs0eS=KC_eW-4YfH)sE9i7u{WzlNK_ zg##?SbPeFEZCTbQ;(Z`qpHgeKm-Y#~`c-Nz_DUEp_NzH-qBqwk@ah-G;~U?QH~sJf D5q3my literal 0 HcmV?d00001 diff --git a/src/docsorter/__pycache__/pptx_ops.cpython-312.pyc b/src/docsorter/__pycache__/pptx_ops.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97774c5c2a057cda1a7d3da8b5a07ddfc6d91cee GIT binary patch literal 8643 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 Q@PSXw-?e>3W%N=0FNCeVlK=n! literal 0 HcmV?d00001 diff --git a/src/docsorter/__pycache__/selection.cpython-312.pyc b/src/docsorter/__pycache__/selection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b154d78b560b7888df085fb5de30efc8506f52f GIT binary patch literal 3476 zcmaJ@TWl2989ry{viDtkvBAc%8RK>AHLzQADGdRNRcxRsZUPM`PSLXL%-XZe?ySy? zuZ&wnqsVPhW0FcNMNv#6l^`R9mr9QM5C}r4)R!erg)?ujl@M|Noa@(1+mr!}!Di>qqDxlyN>w1I*&@pt+1hBw_{i(cY!toU6};Da|NM zj_qS}TpwrgtitEqeQuyRkyku9Z=W~k>+@N>Tk+=teF2QTNURb)Qc(0toD{5w@kO5$ z68#b@1t^#Ch=Dh_zA)HTTXsRP4LxHK28*gu9E&^#57ZmOCi`+qABee3_D8a&o1PyR zbXm=(6wm}-CXy!Q^%Rvn#$XR-HAnJ8DMc2$NdI6Cl--1=#0>VTdXGAo7cCZit5*9S z$!Ap4tLZ7CCqa+RI%8x54zJ(ccAVGOIinlA z$i4y3_a;AGxs7pmKN|NKZqaSHHll;*)EKPH3pZH1?BiWNHJ`p=S?bHyIrK`uZ0R>V z*@hLlL+OnUWq7~o4E0T`^vw>v(mQA4%wzaroR$^6LpgTYs3TeFZ+fO#n{!6N=%T?Q z!*d+&AHzS+4X>hFkp8^m9Yut}=rwflx5H?Jc?}I?(6+DAhB5lKm+E-Myko!j3Gf}0 z(t|{kha_cG$jEt7=!h2z`iNjfp`7m*GDOYU9F57Xf?5fD%U*%-1S4ytVObv#PFF(q zw9u{%778lSHK9WomK8CbBBC%Pr-V#Cm(tS%u{g|416dO^Fq6csLON9_NTQ$sz{IT~ z$7&DCL`v%_85Q!XE@UWl>=DLzdyyvYNH51ZoB@6P^rbC+7n;k+K-tw=kkN1XW-75_ zOYQbJis4?0aj<*9I89IHbU7yhPT_(^#}d9imUvZ@h?Wo~tsqPNXA@~vloI{29#;p+ zuu2p$u4fVh5>YLMsHUymk%UIliB+7Wz`g)4H4_mqn(p~%n*3X=kMol-7hG3;qZ;UnB zd@3iIEL_fj8PEWhB`ukT!_)yPNR1@~8r~*s%M!w- ztv6QT=*(6w!5Nb&5Y6;ki?okKqvO-ia6Uqj$c+5|)sP*api9!;gvW6nl|${^VLTsR zH+kgOKYtL8-4DlR!&@fUx$wHT&i(S-#qs&b^QGASnbDcErM4f=MqVlfUizxOu_(Tm zy^{T)ZnkOrZ2gXj&?A3jQY-df`q`s~*dpU<3VqQKn@rt2`C;-#a^{tfPkeOZ_Q}6) zJNh;*?k_Va*sy@WW`RecTH9fh?eM|L4^Q1VHJ!YTKjrW6p9by(?uI_w)?0B4JqK=^ zLS+wX+B(s@#G|^VV*dX2qotFtf4-E>_-*1?0+&0(x zow;c1^`2`z?;m~;-E}{@Yst&iuKzdVu8x%bD6+LIpsEcMp{Gyw!A71w@xZ$NvlK@) zjZcs(SpTT1uFOII-z5(UfC$qw4s9;A?wNUEW=pB1XEt=GvU0i z2d4B+OS`+SC(M{(8cIo(B%R`fQ{4Xoya*&&{g5IHPdOK5{R~c3|nD6*4(Q;vjz+J z?LlZxY8&I*1UWCXnYhh@9{_DD7zr)JV!VwLJAo#p@z9o;#RnnIB=W4 zJ$n0W>6K%pz;OuVu&<0m`F`^O=YefxwUCFdf-=havwxA7XiCk ze!+u0wR7R;iYJOvDI6_%q7VIb#r4J3l3ysX!qX>VFw!WTUJrJ;**~(-#9Y0Pw}T{x zAJg;}t0tSFvkK_}MLuK0JFkxx;1mfhLI|ySAu#}a+L+Btv{2V9ythu%yie>>f zp{0h&$a&p9Zh9Oy5TQkxeQch+U_Gcv`^iC&(UfVO6a**6e?y@!QQhBB^FLAJztHY4 zQ54YU5I%wzm;^qI=faV)O*FPnrLUZ-Fb95M6Aw9W$+z)7*RaS@>v9I8@4c|VtjEok WBkfz;%NF0c)9RN!mN?K_i~JwKbz!Ff literal 0 HcmV?d00001 diff --git a/src/docsorter/errors.py b/src/docsorter/errors.py deleted file mode 100644 index 7920373..0000000 --- a/src/docsorter/errors.py +++ /dev/null @@ -1,31 +0,0 @@ -class RpcError(Exception): - """Base class for custom RPC errors.""" - code = -32000 - message = "Server error" - - def __init__(self, message: str = None): - super().__init__(message or self.message) - - -class NotFoundError(RpcError): - """Presentation not found.""" - code = -32001 - message = "Not found" - - -class InvalidArgumentError(RpcError): - """Invalid arguments provided.""" - code = -32002 - message = "Invalid argument" - - -class CopyUnsupportedError(RpcError): - """Content cannot be copied safely.""" - code = -32003 - message = "Copy unsupported" - - -class InternalOpError(RpcError): - """Unexpected python-pptx/OPC failure.""" - code = -32004 - message = "Internal operation error" \ No newline at end of file diff --git a/src/docsorter/mcp_client.py b/src/docsorter/mcp_client.py deleted file mode 100644 index 04822ff..0000000 --- a/src/docsorter/mcp_client.py +++ /dev/null @@ -1,78 +0,0 @@ -import argparse -import json -import asyncio -from mcp.client.http import StreamableHttpClient - -def print_json(data): - """Prints a JSON object with indentation.""" - print(json.dumps(data, indent=2)) - -async def main(): - parser = argparse.ArgumentParser(description="A client for the Docsorter MCP server.") - subparsers = parser.add_subparsers(dest="command", required=True) - - # find command - find_parser = subparsers.add_parser("find", help="Find presentations.") - find_parser.add_argument("--start", dest="start_dir", required=True, help="The directory to start searching from.") - find_parser.add_argument("--pattern", help="A pattern to filter results.") - - # list-slides command - list_slides_parser = subparsers.add_parser("list-slides", help="List slides in a presentation.") - list_slides_parser.add_argument("--path", required=True, help="The path to the presentation.") - - # notes command - notes_parser = subparsers.add_parser("notes", help="Get notes from a presentation.") - notes_parser.add_argument("--path", required=True, help="The path to the presentation.") - notes_parser.add_argument("--slides", nargs='+', type=int, help="A list of slide numbers.") - - # copy command - copy_parser = subparsers.add_parser("copy", help="Copy slides between presentations.") - copy_parser.add_argument("--src", dest="src_path", required=True, help="The source presentation path.") - copy_parser.add_argument("--dst", dest="dst_path", required=True, help="The destination presentation path.") - copy_parser.add_argument("--slides", nargs='+', type=int, required=True, help="A list of slide numbers to copy.") - copy_parser.add_argument("--insert", dest="insert_position", type=int, help="The position to insert the copied slides.") - - # delete command - delete_parser = subparsers.add_parser("delete", help="Delete slides from a presentation.") - delete_parser.add_argument("--path", required=True, help="The path to the presentation.") - delete_parser.add_argument("--slides", nargs='+', type=int, required=True, help="A list of slide numbers to delete.") - - args = parser.parse_args() - - client = StreamableHttpClient(url="http://localhost:8000") - - try: - await client.start() - - if args.command == "find": - result = await client.call("presentations/find", {"start_dir": args.start_dir, "pattern": args.pattern}) - print_json(result) - - elif args.command == "list-slides": - result = await client.call("slides/list", {"path": args.path}) - print_json(result) - - elif args.command == "notes": - result = await client.call("slides/notes", {"path": args.path, "slides": args.slides}) - print_json(result) - - elif args.command == "copy": - params = { - "src_path": args.src_path, - "dst_path": args.dst_path, - "slides": args.slides, - } - if args.insert_position: - params["insert_position"] = args.insert_position - result = await client.call("slides/copy", params) - print_json(result) - - elif args.command == "delete": - result = await client.call("slides/delete", {"path": args.path, "slides": args.slides}) - print_json(result) - - finally: - await client.stop() - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file diff --git a/src/docsorter/mcp_server.py b/src/docsorter/mcp_server.py index 10297f9..2715232 100755 --- a/src/docsorter/mcp_server.py +++ b/src/docsorter/mcp_server.py @@ -1,83 +1,77 @@ import sys import logging -from mcp.server import Server -from mcp.common.rpc import JsonRpcError -from mcp.server.stdio import stdio_server +import socket +from fastmcp import FastMCP -from .selection import find_presentations, resolve_presentations -from .pptx_ops import list_slide_titles, list_slide_notes, copy_slides, delete_slides -from .errors import RpcError -from .logging_utils import setup_logging, notify_log +from typing import Optional, List +from docsorter.selection import find_presentations, resolve_presentations +from docsorter.pptx_ops import list_slide_titles, list_slide_notes, copy_slides, delete_slides +from docsorter.logging_utils import setup_logging, notify_log -def map_exceptions_to_jsonrpc(exc: Exception) -> JsonRpcError: - """Maps custom exceptions to JSON-RPC errors.""" - if isinstance(exc, RpcError): - return JsonRpcError(code=exc.code, message=str(exc)) - # Default error for unhandled exceptions - return JsonRpcError(code=-32000, message=f"Internal server error: {exc}") def main() -> None: logger = setup_logging(stream=sys.stderr) - server = Server("docsorter") + mcp = FastMCP("docsorter") - @server.method("presentations/find") - async def m_find(params: dict) -> dict: - notify_log(server, "Searching presentations…") - logger.info(f"Finding presentations with params: {params}") + @mcp.tool() + async def m_find(start_dir: str, pattern: Optional[str] = None, max_results: int = 50) -> dict: + notify_log(mcp, "Searching presentations…") + logger.info(f"Finding presentations with params: start_dir={start_dir}, pattern={pattern}, max_results={max_results}") pres = find_presentations( - params["start_dir"], params.get("pattern"), params.get("max_results", 50) + start_dir, pattern, max_results ) return {"presentations": pres} - @server.method("presentations/resolve") - async def m_resolve(params: dict) -> dict: - notify_log(server, f"Resolving presentation: {params.get('name_or_pattern')}") - logger.info(f"Resolving presentations with params: {params}") + @mcp.tool() + async def m_resolve(start_dir: str, name_or_pattern: str, limit: int = 2) -> dict: + notify_log(mcp, f"Resolving presentation: {name_or_pattern}") + logger.info(f"Resolving presentations with params: start_dir={start_dir}, name_or_pattern={name_or_pattern}, limit={limit}") pres = resolve_presentations( - params["start_dir"], params["name_or_pattern"], params.get("limit", 2) + start_dir, name_or_pattern, limit ) return {"presentations": pres} - @server.method("slides/list") - async def m_list(params: dict) -> dict: - notify_log(server, f"Listing slides for: {params.get('path')}") - logger.info(f"Listing slides for presentation: {params.get('path')}") - slides = list_slide_titles(params["path"]) + @mcp.tool() + async def m_list(path: str) -> dict: + notify_log(mcp, f"Listing slides for: {path}") + logger.info(f"Listing slides for presentation: {path}") + slides = list_slide_titles(path) return {"slides": slides} - @server.method("slides/notes") - async def m_notes(params: dict) -> dict: - notify_log(server, f"Fetching notes for: {params.get('path')}") - logger.info(f"Getting notes for presentation: {params.get('path')}") - notes = list_slide_notes(params["path"], params.get("slides")) + @mcp.tool() + async def m_notes(path: str, slides: Optional[List[int]] = None) -> dict: + notify_log(mcp, f"Fetching notes for: {path}") + logger.info(f"Getting notes for presentation: {path}") + notes = list_slide_notes(path, slides) return {"notes": notes} - @server.method("slides/copy") - async def m_copy(params: dict) -> dict: + @mcp.tool() + async def m_copy(src_path: str, dst_path: str, slides: List[int], insert_position: Optional[int] = None) -> dict: notify_log( - server, - f"Copying {len(params.get('slides', []))} slides from " - f"{params.get('src_path')} to {params.get('dst_path')}" + mcp, + f"Copying {len(slides)} slides from " + f"{src_path} to {dst_path}" ) - logger.info(f"Copying slides with params: {params}") + logger.info(f"Copying slides with params: src_path={src_path}, dst_path={dst_path}, slides={slides}, insert_position={insert_position}") report = copy_slides( - params["src_path"], - params["dst_path"], - params["slides"], - params.get("insert_position") + src_path, + dst_path, + slides, + insert_position ) return {"report": report} - @server.method("slides/delete") - async def m_delete(params: dict) -> dict: - notify_log(server, f"Deleting slides from: {params.get('path')}") - logger.info(f"Deleting slides from presentation: {params.get('path')}") - report = delete_slides(params["path"], params["slides"]) + @mcp.tool() + async def m_delete(path: str, slides: List[int]) -> dict: + notify_log(mcp, f"Deleting slides from: {path}") + logger.info(f"Deleting slides from presentation: {path}") + report = delete_slides(path, slides) return {"report": report} # Centralized error mapping to JSON-RPC codes - server.set_exception_handler(map_exceptions_to_jsonrpc) - # Run stdio JSON-RPC - logger.info("Docsorter MCP server started.") - stdio_server(server).run_forever() \ No newline at end of file + # Run SSE JSON-RPC + port = 59001 + host = "0.0.0.0" + logger.info(f"Docsorter MCP server started at http://{socket.gethostname()}:{port}/sse/") + mcp.run(transport="sse", host=host, port=port) \ No newline at end of file diff --git a/src/mcptest/mcptest.py b/src/mcptest/mcptest.py index f62f9f6..1f8d9e0 100644 --- a/src/mcptest/mcptest.py +++ b/src/mcptest/mcptest.py @@ -1,7 +1,7 @@ from fastmcp import FastMCP -from fastmcp.utilities.http import find_available_port +# from fastmcp.utilities.http import find_available_port import socket -import uvicorn +# import uvicorn mcp = FastMCP("MyAgent")