This is page 5 of 6. Use http://codebase.md/datalayer/jupyter-mcp-server?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .dockerignore ├── .github │ ├── copilot-instructions.md │ ├── dependabot.yml │ └── workflows │ ├── build.yml │ ├── fix-license-header.yml │ ├── lint.sh │ ├── prep-release.yml │ ├── publish-release.yml │ └── test.yml ├── .gitignore ├── .licenserc.yaml ├── .pre-commit-config.yaml ├── .vscode │ ├── mcp.json │ └── settings.json ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dev │ ├── content │ │ ├── new.ipynb │ │ ├── notebook.ipynb │ │ └── README.md │ └── README.md ├── Dockerfile ├── docs │ ├── .gitignore │ ├── .yarnrc.yml │ ├── babel.config.js │ ├── docs │ │ ├── _category_.yaml │ │ ├── clients │ │ │ ├── _category_.yaml │ │ │ ├── claude_desktop │ │ │ │ ├── _category_.yaml │ │ │ │ └── index.mdx │ │ │ ├── cline │ │ │ │ ├── _category_.yaml │ │ │ │ └── index.mdx │ │ │ ├── cursor │ │ │ │ ├── _category_.yaml │ │ │ │ └── index.mdx │ │ │ ├── index.mdx │ │ │ ├── vscode │ │ │ │ ├── _category_.yaml │ │ │ │ └── index.mdx │ │ │ └── windsurf │ │ │ ├── _category_.yaml │ │ │ └── index.mdx │ │ ├── configure │ │ │ ├── _category_.yaml │ │ │ └── index.mdx │ │ ├── contribute │ │ │ ├── _category_.yaml │ │ │ └── index.mdx │ │ ├── deployment │ │ │ ├── _category_.yaml │ │ │ ├── datalayer │ │ │ │ ├── _category_.yaml │ │ │ │ └── streamable-http │ │ │ │ └── index.mdx │ │ │ ├── index.mdx │ │ │ └── jupyter │ │ │ ├── _category_.yaml │ │ │ ├── index.mdx │ │ │ ├── stdio │ │ │ │ ├── _category_.yaml │ │ │ │ └── index.mdx │ │ │ └── streamable-http │ │ │ ├── _category_.yaml │ │ │ ├── jupyter-extension │ │ │ │ └── index.mdx │ │ │ └── standalone │ │ │ └── index.mdx │ │ ├── index.mdx │ │ ├── releases │ │ │ ├── _category_.yaml │ │ │ └── index.mdx │ │ ├── resources │ │ │ ├── _category_.yaml │ │ │ └── index.mdx │ │ └── tools │ │ ├── _category_.yaml │ │ └── index.mdx │ ├── docusaurus.config.js │ ├── LICENSE │ ├── Makefile │ ├── package.json │ ├── README.md │ ├── sidebars.js │ ├── src │ │ ├── components │ │ │ ├── HomepageFeatures.js │ │ │ ├── HomepageFeatures.module.css │ │ │ ├── HomepageProducts.js │ │ │ └── HomepageProducts.module.css │ │ ├── css │ │ │ └── custom.css │ │ ├── pages │ │ │ ├── index.module.css │ │ │ ├── markdown-page.md │ │ │ └── testimonials.tsx │ │ └── theme │ │ └── CustomDocItem.tsx │ └── static │ └── img │ ├── datalayer │ │ ├── logo.png │ │ └── logo.svg │ ├── favicon.ico │ ├── feature_1.svg │ ├── feature_2.svg │ ├── feature_3.svg │ ├── product_1.svg │ ├── product_2.svg │ └── product_3.svg ├── examples │ └── integration_example.py ├── jupyter_mcp_server │ ├── __init__.py │ ├── __main__.py │ ├── __version__.py │ ├── config.py │ ├── enroll.py │ ├── env.py │ ├── jupyter_extension │ │ ├── __init__.py │ │ ├── backends │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── local_backend.py │ │ │ └── remote_backend.py │ │ ├── context.py │ │ ├── extension.py │ │ ├── handlers.py │ │ └── protocol │ │ ├── __init__.py │ │ └── messages.py │ ├── models.py │ ├── notebook_manager.py │ ├── server_modes.py │ ├── server.py │ ├── tools │ │ ├── __init__.py │ │ ├── _base.py │ │ ├── _registry.py │ │ ├── assign_kernel_to_notebook_tool.py │ │ ├── delete_cell_tool.py │ │ ├── execute_cell_tool.py │ │ ├── execute_ipython_tool.py │ │ ├── insert_cell_tool.py │ │ ├── insert_execute_code_cell_tool.py │ │ ├── list_cells_tool.py │ │ ├── list_files_tool.py │ │ ├── list_kernels_tool.py │ │ ├── list_notebooks_tool.py │ │ ├── overwrite_cell_source_tool.py │ │ ├── read_cell_tool.py │ │ ├── read_cells_tool.py │ │ ├── restart_notebook_tool.py │ │ ├── unuse_notebook_tool.py │ │ └── use_notebook_tool.py │ └── utils.py ├── jupyter-config │ ├── jupyter_notebook_config │ │ └── jupyter_mcp_server.json │ └── jupyter_server_config.d │ └── jupyter_mcp_server.json ├── LICENSE ├── Makefile ├── pyproject.toml ├── pytest.ini ├── README.md ├── RELEASE.md ├── smithery.yaml └── tests ├── __init__.py ├── conftest.py ├── test_common.py ├── test_config.py ├── test_jupyter_extension.py ├── test_list_kernels.py ├── test_tools.py └── test_use_notebook.py ``` # Files -------------------------------------------------------------------------------- /docs/static/img/product_2.svg: -------------------------------------------------------------------------------- ``` 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- 3 | ~ Copyright (c) 2023-2024 Datalayer, Inc. 4 | ~ 5 | ~ BSD 3-Clause License 6 | --> 7 | 8 | <svg 9 | xmlns:figma="http://www.figma.com/figma/ns" 10 | xmlns:dc="http://purl.org/dc/elements/1.1/" 11 | xmlns:cc="http://creativecommons.org/ns#" 12 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 13 | xmlns:svg="http://www.w3.org/2000/svg" 14 | xmlns="http://www.w3.org/2000/svg" 15 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 16 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 17 | viewBox="0 0 335.67566 308.79167" 18 | version="1.1" 19 | id="svg1327" 20 | sodipodi:docname="2.svg" 21 | inkscape:version="1.0.1 (c497b03c, 2020-09-10)" 22 | width="335.67566" 23 | height="308.79166"> 24 | <metadata 25 | id="metadata1331"> 26 | <rdf:RDF> 27 | <cc:Work 28 | rdf:about=""> 29 | <dc:format>image/svg+xml</dc:format> 30 | <dc:type 31 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 32 | <dc:title>Web_hosting_SVG</dc:title> 33 | </cc:Work> 34 | </rdf:RDF> 35 | </metadata> 36 | <sodipodi:namedview 37 | pagecolor="#ffffff" 38 | bordercolor="#666666" 39 | borderopacity="1" 40 | objecttolerance="10" 41 | gridtolerance="10" 42 | guidetolerance="10" 43 | inkscape:pageopacity="0" 44 | inkscape:pageshadow="2" 45 | inkscape:window-width="1357" 46 | inkscape:window-height="701" 47 | id="namedview1329" 48 | showgrid="false" 49 | inkscape:zoom="0.98691435" 50 | inkscape:cx="142.90769" 51 | inkscape:cy="115.1505" 52 | inkscape:window-x="0" 53 | inkscape:window-y="25" 54 | inkscape:window-maximized="0" 55 | inkscape:current-layer="svg1327" 56 | inkscape:document-rotation="0" 57 | fit-margin-top="0" 58 | fit-margin-left="0" 59 | fit-margin-right="0" 60 | fit-margin-bottom="0" /> 61 | <defs 62 | id="defs835"> 63 | <style 64 | id="style833">.cls-1,.cls-7{fill:#d6d8e5;}.cls-1{opacity:0.4;}.cls-2{fill:#d5d6e0;}.cls-3{fill:#e9eaf4;}.cls-4{fill:#dfe0ea;}.cls-5{fill:#2b303f;}.cls-6{fill:#8c50ff;}.cls-8{fill:#edf0f9;}.cls-9{fill:#e2e5f2;}.cls-10{fill:#e5e5e5;}.cls-11{fill:#f4f4f4;}.cls-12{fill:#bfbfbf;}.cls-13{fill:#dceeff;}.cls-14{fill:#dbdbdb;}.cls-15{fill:#1e212d;}.cls-16{fill:#ffcea9;}.cls-17{fill:#ededed;}.cls-18{fill:#38226d;}.cls-19{fill:#9c73ff;}.cls-20{fill:#3a2c6d;}</style> 65 | </defs> 66 | <title 67 | id="title837">Web_hosting_SVG</title> 68 | <polygon 69 | class="cls-1" 70 | points="249.36,272.19 251.14,273.21 107.37,356.22 106.48,354.68 " 71 | id="polygon839" 72 | transform="translate(0.00186273,-119.51243)" /> 73 | <path 74 | class="cls-1" 75 | d="m 49.311863,306.76757 -45.8000003,-26.45 c -5.2,-3 -4.55,-8.23 1.45,-11.69 l 23.2500003,-13.43 c 6,-3.46 15.07,-3.84 20.26,-0.84 l 45.8,26.45 c 5.2,3 4.54,8.23 -1.45,11.69 l -23.25,13.43 c -6,3.46 -15.07,3.84 -20.26,0.84 z" 76 | id="path1155" /> 77 | <path 78 | class="cls-7" 79 | d="m 118.10186,273.76757 v -63.94 l -97.749997,-11 v 64.34 0 c -0.07,2 1.06,3.84 3.48,5.24 l 45.8,26.44 c 5.19,3 14.27,2.62 20.26,-0.84 l 23.249997,-13.42 c 3.38,-2 5.06,-4.46 5,-6.82 z" 80 | id="path1157" /> 81 | <path 82 | class="cls-8" 83 | d="m 118.08186,216.80757 v -7 h -7.52 l -41.759997,-24.1 c -5.19,-3 -14.26,-2.62 -20.26,0.84 l -23.25,13.42 a 13.81,13.81 0 0 0 -1.2,0.78 l -3.74,-1.92 c 0,0 0,7.37 0,7.62 v 0 c -0.05,2 1.08,3.83 3.49,5.22 l 45.8,26.44 c 5.19,3 14.27,2.63 20.26,-0.84 l 23.249997,-13.42 c 3.48,-2 5.15,-4.61 4.93,-7 z" 84 | id="path1159" /> 85 | <path 86 | class="cls-9" 87 | d="m 69.631863,230.48757 -45.8,-26.44 c -5.19,-3 -4.54,-8.23 1.46,-11.7 l 23.25,-13.42 c 6,-3.46 15.07,-3.84 20.26,-0.84 l 45.799997,26.44 c 5.19,3 4.54,8.24 -1.46,11.7 l -23.249997,13.44 c -5.99,3.47 -15.07,3.82 -20.26,0.82 z" 88 | id="path1161" /> 89 | <g 90 | id="_Группа_" 91 | data-name="<Группа>" 92 | transform="translate(0.00186273,-119.51243)"> 93 | <path 94 | id="_Контур_" 95 | data-name="<Контур>" 96 | class="cls-10" 97 | d="m 93.16,326 v 0 A 2.25,2.25 0 0 1 92,328 l -21.1,12.18 a 2.26,2.26 0 0 1 -2.25,0 L 39.13,323.07 A 2.24,2.24 0 0 1 38,321.12 V 321 Z" /> 98 | <path 99 | id="_Контур_2" 100 | data-name="<Контур>" 101 | class="cls-10" 102 | d="m 92,328 -21.1,12.18 a 2.24,2.24 0 0 1 -1.12,0.3 V 326 H 93.16 A 2.25,2.25 0 0 1 92,328 Z" /> 103 | <g 104 | id="_Группа_2" 105 | data-name="<Группа>"> 106 | <path 107 | id="_Контур_3" 108 | data-name="<Контур>" 109 | class="cls-11" 110 | d="M 70.61,339 A 1.59,1.59 0 0 1 69,339 L 38.3,321.29 a 0.43,0.43 0 0 1 0,-0.82 L 60.93,307.4 93.16,326 Z" /> 111 | </g> 112 | <g 113 | id="_Группа_3" 114 | data-name="<Группа>"> 115 | <polygon 116 | id="_Контур_4" 117 | data-name="<Контур>" 118 | class="cls-12" 119 | points="50.92,326.01 51.08,326.1 60.93,331.79 66.33,328.67 66.48,328.58 56.48,322.8 " /> 120 | <polygon 121 | id="_Контур_5" 122 | data-name="<Контур>" 123 | class="cls-10" 124 | points="66.33,328.67 56.48,322.98 51.08,326.1 60.93,331.79 " /> 125 | </g> 126 | <path 127 | class="cls-5" 128 | d="M 84.61,327.13 86,328 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.07,0.08 -0.08,0.14 0.03,0.21 z" 129 | id="path1170" /> 130 | <path 131 | class="cls-5" 132 | d="m 82.6,326 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.05 -0.12,0.16 -0.01,0.26 z" 133 | id="path1172" /> 134 | <path 135 | class="cls-5" 136 | d="m 80.59,324.81 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.08 -0.12,0.19 -0.01,0.26 z" 137 | id="path1174" /> 138 | <path 139 | class="cls-5" 140 | d="m 78.58,323.65 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.07 -0.12,0.19 -0.01,0.26 z" 141 | id="path1176" /> 142 | <path 143 | class="cls-5" 144 | d="m 76.57,322.49 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.79 c -0.1,0.06 -0.11,0.18 0,0.25 z" 145 | id="path1178" /> 146 | <path 147 | class="cls-5" 148 | d="m 74.56,321.33 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.1,0.07 -0.11,0.19 0,0.26 z" 149 | id="path1180" /> 150 | <path 151 | class="cls-5" 152 | d="M 72.55,320.17 74,321 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.78 c -0.13,0.09 -0.15,0.19 -0.03,0.26 z" 153 | id="path1182" /> 154 | <path 155 | class="cls-5" 156 | d="m 70.54,319 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.09 -0.12,0.21 0,0.27 z" 157 | id="path1184" /> 158 | <path 159 | class="cls-5" 160 | d="m 68.53,317.84 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.09 -0.12,0.21 0,0.27 z" 161 | id="path1186" /> 162 | <path 163 | class="cls-5" 164 | d="m 66.52,316.68 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.2 0,0.26 z" 165 | id="path1188" /> 166 | <path 167 | class="cls-5" 168 | d="m 64.51,315.52 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.2 0,0.26 z" 169 | id="path1190" /> 170 | <path 171 | class="cls-5" 172 | d="m 62.5,314.36 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.09,0.08 -0.11,0.19 0.01,0.26 z" 173 | id="path1192" /> 174 | <path 175 | class="cls-5" 176 | d="m 60.49,313.2 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.09,0.06 -0.11,0.19 0.01,0.26 z" 177 | id="path1194" /> 178 | <path 179 | class="cls-5" 180 | d="m 57.51,311.48 2.39,1.38 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -2.39,-1.38 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.09,0.08 -0.11,0.19 0.01,0.26 z" 181 | id="path1196" /> 182 | <path 183 | class="cls-5" 184 | d="m 79.68,326.53 1.42,0.82 a 0.53,0.53 0 0 0 0.46,0 l 1.35,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.09 -0.12,0.21 -0.01,0.27 z" 185 | id="path1198" /> 186 | <path 187 | class="cls-5" 188 | d="m 77.67,325.37 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.19 0,0.26 z" 189 | id="path1200" /> 190 | <path 191 | class="cls-5" 192 | d="m 75.65,324.21 1.43,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -1.43,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.1,0.05 -0.11,0.19 0,0.26 z" 193 | id="path1202" /> 194 | <path 195 | class="cls-5" 196 | d="m 73.64,323.05 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 L 75.48,322 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.13,0.08 -0.15,0.22 -0.03,0.27 z" 197 | id="path1204" /> 198 | <path 199 | class="cls-5" 200 | d="m 71.64,321.89 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.08 -0.12,0.2 -0.01,0.26 z" 201 | id="path1206" /> 202 | <path 203 | class="cls-5" 204 | d="m 69.63,320.73 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.2 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.19 0,0.26 z" 205 | id="path1208" /> 206 | <path 207 | class="cls-5" 208 | d="m 67.61,319.57 1.42,0.82 a 0.57,0.57 0 0 0 0.46,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.27 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.35,0.79 c -0.11,0.07 -0.12,0.19 -0.01,0.26 z" 209 | id="path1210" /> 210 | <path 211 | class="cls-5" 212 | d="m 65.6,318.4 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.2 0,0.26 z" 213 | id="path1212" /> 214 | <path 215 | class="cls-5" 216 | d="m 63.59,317.24 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.1,0.08 -0.11,0.19 0.01,0.26 z" 217 | id="path1214" /> 218 | <path 219 | class="cls-5" 220 | d="m 61.57,316.08 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 L 63.41,315 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.79 c -0.13,0.1 -0.14,0.21 -0.03,0.29 z" 221 | id="path1216" /> 222 | <path 223 | class="cls-5" 224 | d="m 59.56,314.91 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.14,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.09 -0.12,0.21 0,0.27 z" 225 | id="path1218" /> 226 | <path 227 | class="cls-5" 228 | d="m 57.54,313.75 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.08 -0.12,0.2 -0.01,0.26 z" 229 | id="path1220" /> 230 | <path 231 | class="cls-5" 232 | d="m 81.7,327.7 2.39,1.38 a 0.55,0.55 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.27 l -2.39,-1.38 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.09,0.07 -0.11,0.19 0.01,0.26 z" 233 | id="path1222" /> 234 | <path 235 | class="cls-5" 236 | d="m 57.38,311.54 1.42,0.82 c 0.11,0.06 0.11,0.17 0,0.24 l -1.44,0.83 a 0.48,0.48 0 0 1 -0.42,0 l -0.11,-0.07 a 0.45,0.45 0 0 0 -0.39,0 l -1.51,0.88 a 0.45,0.45 0 0 1 -0.42,0 l -0.91,-0.52 a 0.14,0.14 0 0 1 0,-0.25 l 1.42,-0.81 0.29,-0.17 0.21,-0.13 0.22,-0.12 1.22,-0.7 a 0.45,0.45 0 0 1 0.42,0 z" 237 | id="path1224" /> 238 | <path 239 | class="cls-5" 240 | d="m 76.81,331.63 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.1,0.07 -0.11,0.19 0,0.26 z" 241 | id="path1226" /> 242 | <path 243 | class="cls-5" 244 | d="m 74.81,330.47 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.1,0.08 -0.11,0.19 0.01,0.26 z" 245 | id="path1228" /> 246 | <path 247 | class="cls-5" 248 | d="m 72.8,329.31 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.11,0.2 0,0.26 z" 249 | id="path1230" /> 250 | <path 251 | class="cls-5" 252 | d="m 55.73,319.45 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.35,-0.79 c 0.14,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.09,0.09 -0.11,0.21 0.01,0.27 z" 253 | id="path1232" /> 254 | <path 255 | class="cls-5" 256 | d="m 53.72,318.3 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.1,0.07 -0.11,0.19 0,0.26 z" 257 | id="path1234" /> 258 | <path 259 | class="cls-5" 260 | d="m 51.72,317.14 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.09,0.12 -0.11,0.19 0.01,0.26 z" 261 | id="path1236" /> 262 | <path 263 | class="cls-5" 264 | d="m 49.71,316 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.06 -0.11,0.17 0,0.26 z" 265 | id="path1238" /> 266 | <path 267 | class="cls-5" 268 | d="m 70.28,327.86 1.94,1.14 a 0.51,0.51 0 0 0 0.45,0 l 1.35,-0.79 c 0.14,-0.07 0.15,-0.19 0,-0.26 l -1.94,-1.12 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.09,0.05 -0.11,0.17 0.01,0.24 z" 269 | id="path1240" /> 270 | <path 271 | class="cls-5" 272 | d="m 57.73,320.61 1.94,1.12 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.2 0,-0.26 l -1.94,-1.12 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.11,0.19 0,0.26 z" 273 | id="path1242" /> 274 | <path 275 | class="cls-5" 276 | d="m 60.25,322.07 9.45,5.45 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 L 62.09,321 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.79 c -0.14,0.09 -0.15,0.21 -0.04,0.28 z" 277 | id="path1244" /> 278 | <path 279 | class="cls-5" 280 | d="m 77.19,327.34 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.07 0.14,-0.19 0,-0.26 L 79,326.28 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.1 -0.12,0.22 0,0.28 z" 281 | id="path1246" /> 282 | <path 283 | class="cls-5" 284 | d="M 75.18,326.18 76.6,327 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 L 77,325.12 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.11,0.1 -0.13,0.22 -0.01,0.28 z" 285 | id="path1248" /> 286 | <path 287 | class="cls-5" 288 | d="m 73.17,325 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 L 76.4,325 c 0.13,-0.07 0.15,-0.19 0,-0.26 L 75,324 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.12,0.06 -0.14,0.22 -0.02,0.22 z" 289 | id="path1250" /> 290 | <path 291 | class="cls-5" 292 | d="m 71.16,323.86 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 L 73,322.8 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.13,0.1 -0.14,0.22 -0.03,0.28 z" 293 | id="path1252" /> 294 | <path 295 | class="cls-5" 296 | d="m 69.15,322.71 1.42,0.82 a 0.57,0.57 0 0 0 0.46,0 l 1.35,-0.79 c 0.14,-0.07 0.15,-0.19 0,-0.26 L 71,321.64 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.78 c -0.15,0.1 -0.2,0.22 -0.05,0.29 z" 297 | id="path1254" /> 298 | <path 299 | class="cls-5" 300 | d="m 67.15,321.55 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.35,-0.79 c 0.14,-0.07 0.15,-0.19 0,-0.26 L 69,320.48 a 0.57,0.57 0 0 0 -0.46,0 l -1.35,0.78 c -0.19,0.1 -0.19,0.22 -0.04,0.29 z" 301 | id="path1256" /> 302 | <path 303 | class="cls-5" 304 | d="m 65.14,320.39 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.27 L 67,319.32 a 0.57,0.57 0 0 0 -0.46,0 l -1.35,0.79 C 65,320.2 65,320.32 65.14,320.39 Z" 305 | id="path1258" /> 306 | <path 307 | class="cls-5" 308 | d="m 63.13,319.23 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.2 0,-0.27 L 65,318.16 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 C 63,319 63,319.16 63.13,319.23 Z" 309 | id="path1260" /> 310 | <path 311 | class="cls-5" 312 | d="m 61.12,318.07 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 L 63,317 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 C 61,317.88 61,318 61.12,318.07 Z" 313 | id="path1262" /> 314 | <path 315 | class="cls-5" 316 | d="m 59.11,316.91 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 L 61,315.85 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.79 c -0.19,0.08 -0.19,0.2 -0.08,0.27 z" 317 | id="path1264" /> 318 | <path 319 | class="cls-5" 320 | d="m 57.1,315.75 1.42,0.82 a 0.53,0.53 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.79 C 57,315.56 57,315.68 57.1,315.75 Z" 321 | id="path1266" /> 322 | <path 323 | class="cls-5" 324 | d="m 55.09,314.59 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.79 c -0.1,0.06 -0.1,0.18 -0.01,0.25 z" 325 | id="path1268" /> 326 | <path 327 | class="cls-5" 328 | d="m 79.2,328.5 2.93,1.7 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 L 81,327.44 a 0.5,0.5 0 0 0 -0.46,0 l -1.35,0.78 c -0.1,0.1 -0.11,0.22 0.01,0.28 z" 329 | id="path1270" /> 330 | <path 331 | class="cls-5" 332 | d="m 76.24,329 1.42,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.35,-0.79 c 0.14,-0.07 0.15,-0.19 0,-0.26 L 78.08,328 a 0.53,0.53 0 0 0 -0.46,0 l -1.35,0.78 c -0.14,0.08 -0.15,0.22 -0.03,0.22 z" 333 | id="path1272" /> 334 | <path 335 | class="cls-5" 336 | d="m 74.23,327.89 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.14,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.2 0,0.27 z" 337 | id="path1274" /> 338 | <path 339 | class="cls-5" 340 | d="m 72.22,326.73 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.79 c 0.13,-0.07 0.15,-0.19 0,-0.26 l -1.42,-0.82 a 0.51,0.51 0 0 0 -0.45,0 l -1.36,0.78 c -0.1,0.08 -0.12,0.2 0,0.27 z" 341 | id="path1276" /> 342 | <path 343 | class="cls-5" 344 | d="m 70.21,325.57 1.42,0.82 a 0.55,0.55 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 l -1.42,-0.82 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.1,0.07 -0.11,0.19 0,0.26 z" 345 | id="path1278" /> 346 | <path 347 | class="cls-5" 348 | d="m 68.2,324.41 1.42,0.82 a 0.53,0.53 0 0 0 0.46,0 l 1.35,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.27 L 70,323.34 a 0.55,0.55 0 0 0 -0.45,0 l -1.35,0.79 c -0.1,0.09 -0.11,0.21 0,0.28 z" 349 | id="path1280" /> 350 | <path 351 | class="cls-5" 352 | d="m 66.19,323.25 1.43,0.82 a 0.51,0.51 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 L 68,322.19 a 0.51,0.51 0 0 0 -0.45,0 l -1.35,0.79 c -0.11,0.08 -0.12,0.2 -0.01,0.27 z" 353 | id="path1282" /> 354 | <path 355 | class="cls-5" 356 | d="m 64.19,322.09 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.2 0,-0.26 L 66,321 a 0.53,0.53 0 0 0 -0.46,0 l -1.35,0.79 c -0.1,0.11 -0.12,0.21 0,0.3 z" 357 | id="path1284" /> 358 | <path 359 | class="cls-5" 360 | d="m 62.18,320.93 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.14,-0.2 0,-0.26 L 64,319.87 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.11,0.1 -0.13,0.21 -0.01,0.28 z" 361 | id="path1286" /> 362 | <path 363 | class="cls-5" 364 | d="m 60.17,319.77 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 L 62,318.71 a 0.48,0.48 0 0 0 -0.45,0 l -1.36,0.78 c -0.12,0.1 -0.14,0.21 -0.02,0.28 z" 365 | id="path1288" /> 366 | <path 367 | class="cls-5" 368 | d="m 58.16,318.61 1.42,0.82 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.2 0,-0.26 L 60,317.55 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.14,0.1 -0.15,0.21 -0.04,0.28 z" 369 | id="path1290" /> 370 | <path 371 | class="cls-5" 372 | d="m 56.15,317.45 1.42,0.82 a 0.5,0.5 0 0 0 0.46,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 L 58,316.39 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.15,0.1 -0.2,0.21 -0.05,0.28 z" 373 | id="path1292" /> 374 | <path 375 | class="cls-5" 376 | d="m 51.66,314.86 3.91,2.25 a 0.48,0.48 0 0 0 0.45,0 l 1.35,-0.78 c 0.14,-0.08 0.15,-0.19 0,-0.26 l -3.91,-2.26 a 0.55,0.55 0 0 0 -0.45,0 l -1.36,0.79 c -0.09,0.07 -0.11,0.19 0.01,0.26 z" 377 | id="path1294" /> 378 | <path 379 | class="cls-5" 380 | d="m 78.24,330.2 1.94,1.12 a 0.48,0.48 0 0 0 0.45,0 l 1.36,-0.78 c 0.13,-0.08 0.15,-0.19 0,-0.26 l -1.94,-1.12 a 0.48,0.48 0 0 0 -0.45,0 l -1.35,0.78 c -0.11,0.06 -0.12,0.2 -0.01,0.26 z" 381 | id="path1296" /> 382 | </g> 383 | <g 384 | id="_Группа_4" 385 | data-name="<Группа>" 386 | transform="translate(0.00186273,-119.51243)"> 387 | <g 388 | id="_Группа_5" 389 | data-name="<Группа>"> 390 | <path 391 | id="_Контур_6" 392 | data-name="<Контур>" 393 | class="cls-10" 394 | d="m 93.16,326 v 0 L 60.93,307.4 v -25.35 l 0.25,-0.56 0.26,-0.14 v 0 a 2.27,2.27 0 0 1 2.23,0 l 29.49,17 a 2.22,2.22 0 0 1 1.11,1.93 v 23.78 c -0.2,1.26 -0.42,1.55 -1.11,1.94 z" /> 395 | <path 396 | id="_Контур_7" 397 | data-name="<Контур>" 398 | class="cls-10" 399 | d="M 90.2,301.45 60.93,282.9 v -0.85 l 0.25,-0.56 0.26,-0.14 a 2.27,2.27 0 0 1 2.23,0 l 29.49,17 a 2.25,2.25 0 0 1 0.85,0.88 z" /> 400 | <path 401 | id="_Контур_8" 402 | data-name="<Контур>" 403 | class="cls-13" 404 | d="m 61.67,281.48 31,17.87 a 1.08,1.08 0 0 1 0.54,0.94 V 326 L 60.93,307.4 v -25.49 a 0.49,0.49 0 0 1 0.74,-0.43 z" /> 405 | <path 406 | id="_Контур_9" 407 | data-name="<Контур>" 408 | class="cls-11" 409 | d="m 61.67,281.48 31,17.87 a 1.08,1.08 0 0 1 0.54,0.94 V 326 L 60.93,307.4 v -25.49 a 0.49,0.49 0 0 1 0.74,-0.43 z" /> 410 | <polygon 411 | id="_Контур_10" 412 | data-name="<Контур>" 413 | class="cls-14" 414 | points="60.93,306.48 60.93,307.4 93.16,326.01 93.16,325.09 " /> 415 | </g> 416 | <polygon 417 | id="_Контур_11" 418 | data-name="<Контур>" 419 | class="cls-15" 420 | points="92.12,323.11 92.12,300.34 62.16,283.04 62.16,305.86 " /> 421 | </g> 422 | <g 423 | id="Men_2" 424 | transform="translate(0.00186273,-119.51243)"> 425 | <path 426 | id="_Контур_12" 427 | data-name="<Контур>" 428 | class="cls-16" 429 | d="m 8.66,303.64 c -0.27,2.4 -3.13,12.72 -2.51,16.25 0.62,3.53 10.36,9.45 10.36,9.45 l 1.35,-6 -5.4,-5.34 1.46,-10 z" /> 430 | <path 431 | id="_Контур_13" 432 | data-name="<Контур>" 433 | class="cls-17" 434 | d="m 16.21,293.47 a 4.27,4.27 0 0 0 -5.43,1.64 c -1.44,2.38 -2.93,10.53 -3.06,11.89 0,0 2,2.78 5,2.32 z" /> 435 | <path 436 | class="cls-18" 437 | d="m 27.34,408.56 a 8.36,8.36 0 0 0 6.31,-0.77 c 1.52,0 9.78,-3.48 10.9,-0.52 1,2.6 -2.59,5 -4.5,5.47 -4.17,1 -7.25,3.09 -8.93,3.44 -1.25,0.26 -2.86,0.43 -3.85,-0.55 -1.2,-1.17 -1.09,-5.52 0.07,-7.07 z" 438 | id="path1309" /> 439 | <path 440 | class="cls-19" 441 | d="m 31.12,415.46 c 1.68,-0.35 4.76,-2.43 8.93,-3.44 1.6,-0.39 4.35,-2.14 4.63,-4.23 0.37,2.4 -2.85,4.52 -4.63,4.95 -4.17,1 -7.25,3.09 -8.93,3.44 -1.25,0.26 -2.86,0.43 -3.85,-0.55 a 2.34,2.34 0 0 1 -0.55,-1 5,5 0 0 0 4.4,0.83 z" 442 | id="path1311" /> 443 | <path 444 | class="cls-18" 445 | d="m 14,402.55 a 8.38,8.38 0 0 0 6.31,-0.77 c 1.52,0.05 9.77,-3.48 10.89,-0.52 1,2.6 -2.59,5 -4.5,5.47 -4.16,1 -7.25,3.09 -8.92,3.44 -1.25,0.26 -2.86,0.43 -3.86,-0.55 -1.23,-1.17 -1.13,-5.52 0.08,-7.07 z" 446 | id="path1313" /> 447 | <path 448 | class="cls-19" 449 | d="m 17.74,409.45 c 1.67,-0.35 4.76,-2.43 8.92,-3.44 1.6,-0.39 4.36,-2.14 4.64,-4.23 0.37,2.4 -2.85,4.52 -4.64,5 -4.16,1 -7.25,3.09 -8.92,3.44 -1.25,0.26 -2.86,0.43 -3.86,-0.55 a 2.33,2.33 0 0 1 -0.54,-1 5,5 0 0 0 4.4,0.78 z" 450 | id="path1315" /> 451 | <path 452 | id="_Контур_14" 453 | data-name="<Контур>" 454 | class="cls-6" 455 | d="m 13.8,380.13 a 49.34,49.34 0 0 1 1.9,-9 c 0,0 -0.57,-7.8 -0.85,-15.2 -0.33,-8.39 -3.24,-15.77 -0.94,-22.47 l 25.89,5.76 c 0,0 -1.56,33.45 -1.91,37.5 a 76.61,76.61 0 0 1 -1.16,9.4 c -1.18,6.49 -3.1,21.94 -3.1,21.94 -2.74,1.59 -6.34,0.71 -6.34,0.71 0,0 0.2,-19.54 0.38,-24.22 0.21,-5.71 0.39,-5.27 0.39,-5.27 L 27.2,365 26.64,357.92 c 0,0 -0.72,5.2 -1.18,9.84 -0.41,4.05 -1.39,7.71 -2.48,15.24 -0.94,6.54 -2.48,19.67 -2.48,19.67 -2.74,1.59 -6.46,0.27 -6.46,0.27 0,0 -0.98,-17.14 -0.24,-22.81 z" /> 456 | <path 457 | id="_Контур_15" 458 | data-name="<Контур>" 459 | class="cls-16" 460 | d="m 17.58,293.61 c 2,0.1 3.44,0.61 3.68,0 a 24,24 0 0 0 0.45,-3.08 c -0.22,-0.57 -0.42,-1.18 -0.42,-1.18 -2.48,-1.53 -3.26,-4.2 -3.58,-7.32 -0.56,-5.33 2.32,-10 7.65,-10.55 5,-0.52 8.69,3 9.67,7.83 0.53,2.39 1.58,6.9 0.16,10.82 -0.8,2.2 -1.82,3.94 -2.85,4.28 a 26.44,26.44 0 0 1 -3,-0.26 v 0 c 0,0 -0.27,1.48 -0.45,2.43 -0.18,0.95 -0.11,1.45 1.78,2.61 1.89,1.16 -2.86,3.56 -6,3.33 -3.14,-0.23 -6.6,-2.11 -7.55,-4.12 -0.99,-2.22 -0.9,-4.85 0.46,-4.79 z" /> 461 | <path 462 | id="_Контур_16" 463 | data-name="<Контур>" 464 | class="cls-11" 465 | d="m 20.43,293.9 c -0.55,1.37 0.84,3 4.55,4.2 3.71,1.2 4,-0.34 4,-0.34 a 74.4,74.4 0 0 1 7.2,3.71 c 2.22,1.56 3.21,6 3.51,13.64 0.34,8.79 0.37,21.82 0.08,24.07 0,0 -4.15,4.14 -9.47,3.7 -5.32,-0.44 -14.26,-5.14 -16.43,-8.77 0.06,-7.7 1,-9 -0.28,-13.4 -2.86,-10.15 -4.36,-14.31 -3,-21.06 1.14,-5.57 2.74,-6.26 4.94,-6.2 a 45.35,45.35 0 0 1 4.9,0.45 z" /> 466 | <path 467 | id="_Контур_17" 468 | data-name="<Контур>" 469 | class="cls-16" 470 | d="m 48.07,322.38 c -4.36,1.1 -5.38,-1.66 -6,-3.93 -1.38,-5.52 -2.13,-10.5 -3.17,-13.63 -1.21,-3.69 -2.49,-4.28 -4.29,-5 -2.1,-0.84 -3.91,0.94 -3.21,5.64 a 94.91,94.91 0 0 0 4,16 c 0.55,1.72 1.77,4.77 3.59,6.25 2.25,1.82 5.64,1.74 10.77,0.46 2.18,-0.54 4.58,-1.85 8.72,-3.78 1.11,-0.52 2,-0.86 4.11,-1.85 a 14.64,14.64 0 0 0 5.47,-4.27 c 1.11,-1.62 1.26,-2.38 1,-2.75 -0.26,-0.37 -0.79,-0.36 -1.38,0.32 a 12.12,12.12 0 0 1 -3.14,2.91 c 0,0 1.37,-1.43 2.12,-2.35 a 12,12 0 0 0 1.65,-2.7 c 0.39,-0.94 -0.42,-2.22 -1,-1.55 -0.58,0.67 -0.92,1.36 -2,2.71 a 11.58,11.58 0 0 1 -2,1.93 22.06,22.06 0 0 0 1.87,-3 5,5 0 0 0 0.68,-2.71 c 0,-0.49 -0.73,-1.08 -1.39,-0.14 a 17.61,17.61 0 0 1 -2.34,3.58 c -1,1 -1.84,1.63 -1.87,1.34 -0.03,-0.29 0.61,-0.86 1,-2.06 0.39,-1.2 0,-2.48 -0.66,-2.61 -0.66,-0.13 -0.54,-0.11 -1.08,1 a 35.32,35.32 0 0 0 -2,3.6 7.42,7.42 0 0 1 -1.79,3.09 c -1.14,1.19 -3.51,2.45 -7.66,3.5 z" /> 471 | <path 472 | id="_Контур_18" 473 | data-name="<Контур>" 474 | class="cls-17" 475 | d="m 33,299.07 c 2.91,-0.43 5,0.88 6.26,5.29 1.26,4.41 2.18,8 2.18,8 a 7.89,7.89 0 0 1 -4.88,3.24 c -3.48,0.85 -4.74,-0.82 -4.74,-0.82 0,0 -1,-4.62 -1.57,-7.77 -0.57,-3.15 -0.81,-7.41 2.75,-7.94 z" /> 476 | <path 477 | class="cls-20" 478 | d="m 34.86,278.63 c 0,0 4.6,-7 -5.37,-8.43 -7.19,-1 -11.81,3.6 -12.35,8.61 -0.51,4.79 2.44,9.77 4.57,11.74 1,0.37 3.28,0.59 6.22,-0.75 a 35.4,35.4 0 0 0 0.14,-3.92 c 0,0 -3.51,-7.38 6.79,-7.25 z" 479 | id="path1322" /> 480 | </g> 481 | <g 482 | id="Canvas" 483 | transform="matrix(4.0495342,0,0,4.0495342,-6480.5841,-9936.3011)" 484 | figma:type="canvas"> 485 | <g 486 | id="g2566" 487 | style="mix-blend-mode:normal" 488 | figma:type="group"> 489 | <g 490 | id="g2564" 491 | style="mix-blend-mode:normal" 492 | figma:type="group"> 493 | <g 494 | id="Group" 495 | style="mix-blend-mode:normal" 496 | figma:type="group"> 497 | <g 498 | id="g" 499 | style="mix-blend-mode:normal" 500 | figma:type="group"> 501 | <g 502 | id="path" 503 | style="mix-blend-mode:normal" 504 | figma:type="group"> 505 | <g 506 | id="path9 fill" 507 | style="mix-blend-mode:normal" 508 | figma:type="vector"> 509 | <path 510 | id="use2501" 511 | d="m 1642.285,2479.8353 c 0,1.5581 -0.1247,2.0655 -0.4452,2.4394 -0.3567,0.3213 -0.8198,0.4989 -1.2998,0.4986 l 0.1246,0.8903 c 0.7442,0.01 1.4664,-0.2528 2.0299,-0.7389 0.3033,-0.3698 0.5289,-0.7969 0.6635,-1.2558 0.1346,-0.4588 0.1754,-0.9401 0.12,-1.4151 v -5.8938 h -1.193 v 5.4397 z" 512 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 513 | </g> 514 | </g> 515 | <g 516 | id="g2508" 517 | style="mix-blend-mode:normal" 518 | figma:type="group"> 519 | <g 520 | id="path10 fill" 521 | style="mix-blend-mode:normal" 522 | figma:type="vector"> 523 | <path 524 | id="use2505" 525 | d="m 1651.182,2479.1331 c 0,0.6677 0,1.2642 0.053,1.7806 h -1.0595 l -0.071,-1.0595 c -0.2216,0.3749 -0.5385,0.6844 -0.9185,0.897 -0.38,0.2127 -0.8095,0.321 -1.2449,0.3138 -1.0328,0 -2.2614,-0.5609 -2.2614,-2.8489 v -3.8016 h 1.193 v 3.5612 c 0,1.2375 0.3828,2.0655 1.4601,2.0655 0.2216,0 0.4415,-0.04 0.6467,-0.1232 0.2053,-0.084 0.3917,-0.2076 0.5484,-0.3643 0.1567,-0.1568 0.2806,-0.3432 0.3643,-0.5484 0.084,-0.2053 0.1256,-0.4251 0.1233,-0.6468 v -3.9885 h 1.1929 v 4.7275 z" 526 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 527 | </g> 528 | </g> 529 | <g 530 | id="g2513" 531 | style="mix-blend-mode:normal" 532 | figma:type="group"> 533 | <g 534 | id="path11 fill" 535 | style="mix-blend-mode:normal" 536 | figma:type="vector"> 537 | <path 538 | id="use2510" 539 | d="m 1653.4434,2476.5326 c 0,-0.8279 0,-1.5046 -0.053,-2.1189 h 1.0684 l 0.053,1.1129 c 0.238,-0.4021 0.5807,-0.732 0.9915,-0.9546 0.4107,-0.2227 0.8742,-0.3297 1.3411,-0.3096 1.5847,0 2.7777,1.3265 2.7777,3.303 0,2.3326 -1.4334,3.49 -2.9825,3.49 -0.3965,0.018 -0.7909,-0.067 -1.1449,-0.2467 -0.3541,-0.1793 -0.6558,-0.447 -0.8761,-0.7772 v 0 3.5612 h -1.1752 v -7.0333 z m 1.1752,1.7361 c 0,0.1616 0.021,0.3225 0.053,0.4808 0.101,0.3953 0.331,0.7456 0.6535,0.9956 0.3225,0.2499 0.7191,0.3852 1.1271,0.3843 1.2553,0 1.9943,-1.0238 1.9943,-2.5106 0,-1.2998 -0.6944,-2.4127 -1.9498,-2.4127 -0.4967,0.041 -0.9616,0.2612 -1.3074,0.6201 -0.3459,0.3589 -0.5489,0.8317 -0.5711,1.3297 z" 540 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 541 | </g> 542 | </g> 543 | <g 544 | id="g2518" 545 | style="mix-blend-mode:normal" 546 | figma:type="group"> 547 | <g 548 | id="path12 fill" 549 | style="mix-blend-mode:normal" 550 | figma:type="vector"> 551 | <path 552 | id="use2515" 553 | d="m 1661.7476,2474.4078 1.4334,3.8372 c 0.1514,0.4273 0.3116,0.9437 0.4185,1.3265 0.1246,-0.3917 0.2581,-0.8903 0.4184,-1.3532 l 1.2998,-3.8105 h 1.2554 l -1.7806,4.6296 c -0.8903,2.2257 -1.4334,3.3742 -2.2525,4.0686 -0.4125,0.3768 -0.9155,0.6406 -1.4601,0.7657 l -0.2938,-0.9972 c 0.3808,-0.1251 0.7343,-0.3215 1.0417,-0.5787 0.4343,-0.3539 0.779,-0.8054 1.006,-1.3176 0.049,-0.089 0.082,-0.1851 0.098,-0.2849 -0.01,-0.1074 -0.037,-0.2126 -0.08,-0.3116 l -2.4216,-5.9917 h 1.2998 z" 554 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 555 | </g> 556 | </g> 557 | <g 558 | id="g2523" 559 | style="mix-blend-mode:normal" 560 | figma:type="group"> 561 | <g 562 | id="path13 fill" 563 | style="mix-blend-mode:normal" 564 | figma:type="vector"> 565 | <path 566 | id="use2520" 567 | d="m 1669.7401,2472.54 v 1.8696 h 1.7094 v 0.8903 h -1.7094 v 3.5078 c 0,0.8013 0.2315,1.2642 0.8903,1.2642 0.234,0 0.4675,-0.023 0.6945,-0.08 l 0.053,0.8903 c -0.3404,0.1179 -0.6995,0.1722 -1.0595,0.1602 -0.2384,0.015 -0.4772,-0.022 -0.7,-0.1079 -0.2229,-0.086 -0.4244,-0.2193 -0.5909,-0.3906 -0.3626,-0.4851 -0.5281,-1.0895 -0.463,-1.6916 v -3.5612 h -1.0149 v -0.8903 h 1.0327 v -1.5847 z" 568 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 569 | </g> 570 | </g> 571 | <g 572 | id="g2528" 573 | style="mix-blend-mode:normal" 574 | figma:type="group"> 575 | <g 576 | id="path14 fill" 577 | style="mix-blend-mode:normal" 578 | figma:type="vector"> 579 | <path 580 | id="use2525" 581 | d="m 1673.6472,2477.869 c -0.024,0.3019 0.017,0.6055 0.1221,0.8898 0.1047,0.2842 0.2698,0.5423 0.484,0.7565 0.2142,0.2142 0.4723,0.3793 0.7566,0.484 0.2842,0.1046 0.5878,0.1463 0.8897,0.1222 0.6107,0.014 1.2175,-0.1017 1.7806,-0.3384 l 0.2048,0.8903 c -0.6911,0.2847 -1.4341,0.4212 -2.1812,0.4007 -0.4356,0.03 -0.8724,-0.035 -1.2806,-0.1898 -0.4081,-0.1549 -0.778,-0.3962 -1.0841,-0.7074 -0.3062,-0.3112 -0.5414,-0.685 -0.6895,-1.0956 -0.1481,-0.4107 -0.2057,-0.8485 -0.1687,-1.2835 0,-1.9587 1.1663,-3.5078 3.0715,-3.5078 2.1367,0 2.6709,1.8696 2.6709,3.0626 0.011,0.1838 0.011,0.3682 0,0.552 h -4.6028 z m 3.4899,-0.8903 c 0.034,-0.238 0.017,-0.4806 -0.05,-0.7115 -0.067,-0.2309 -0.1834,-0.4447 -0.3403,-0.6269 -0.1569,-0.1822 -0.3511,-0.3287 -0.5694,-0.4296 -0.2183,-0.1008 -0.4557,-0.1537 -0.6962,-0.155 -0.4892,0.035 -0.9475,0.2522 -1.2851,0.6079 -0.3376,0.3558 -0.5302,0.8248 -0.54,1.3151 z" 582 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 583 | </g> 584 | </g> 585 | <g 586 | id="g2533" 587 | style="mix-blend-mode:normal" 588 | figma:type="group"> 589 | <g 590 | id="path15 fill" 591 | style="mix-blend-mode:normal" 592 | figma:type="vector"> 593 | <path 594 | id="use2530" 595 | d="m 1680.0334,2476.4323 c 0,-0.7657 0,-1.4245 -0.053,-2.0299 h 1.0684 v 1.2731 h 0.053 c 0.1121,-0.3929 0.3438,-0.7412 0.6629,-0.9965 0.3191,-0.2552 0.7097,-0.4048 1.1177,-0.4279 0.1123,-0.015 0.226,-0.015 0.3383,0 v 1.1128 c -0.1361,-0.016 -0.2735,-0.016 -0.4096,0 -0.4041,0.016 -0.7887,0.1779 -1.082,0.4565 -0.2933,0.2785 -0.4751,0.6542 -0.5116,1.057 -0.033,0.1822 -0.051,0.3668 -0.053,0.552 v 3.4633 h -1.1752 v -4.4515 z" 596 | style="mix-blend-mode:normal;fill:#4e4e4e" /> 597 | </g> 598 | </g> 599 | </g> 600 | </g> 601 | <g 602 | id="g2562" 603 | style="mix-blend-mode:normal" 604 | figma:type="group"> 605 | <g 606 | id="g2540" 607 | style="mix-blend-mode:normal" 608 | figma:type="group"> 609 | <g 610 | id="path16 fill" 611 | style="mix-blend-mode:normal" 612 | figma:type="vector"> 613 | <path 614 | id="use2537" 615 | d="m 1679.5106,2456.5257 c 0.037,0.5981 -0.1057,1.1935 -0.4088,1.7105 -0.303,0.5169 -0.7531,0.9319 -1.2929,1.1922 -0.5397,0.2602 -1.1447,0.3539 -1.7379,0.2691 -0.5932,-0.085 -1.1477,-0.3442 -1.593,-0.7453 -0.4452,-0.4011 -0.7609,-0.9256 -0.907,-1.5067 -0.146,-0.5812 -0.1158,-1.1927 0.087,-1.7566 0.2027,-0.5639 0.5686,-1.0547 1.0513,-1.4099 0.4826,-0.3551 1.06,-0.5586 1.6586,-0.5845 0.3926,-0.022 0.7855,0.035 1.1562,0.1654 0.3707,0.1308 0.7119,0.3336 1.0039,0.5967 0.2921,0.2631 0.5293,0.5813 0.6979,0.9364 0.1687,0.3551 0.2654,0.74 0.2848,1.1327 z" 616 | style="mix-blend-mode:normal;fill:#767677" /> 617 | </g> 618 | </g> 619 | <g 620 | id="g2545" 621 | style="mix-blend-mode:normal" 622 | figma:type="group"> 623 | <g 624 | id="path17 fill" 625 | style="mix-blend-mode:normal" 626 | figma:type="vector"> 627 | <path 628 | id="use2542" 629 | d="m 1661.9062,2491.3924 c -8.0126,0 -15.0549,-2.8757 -18.6962,-7.1224 1.4128,3.8204 3.9622,7.1163 7.3048,9.444 3.3426,2.3278 7.3182,3.5756 11.3914,3.5756 4.0733,0 8.0488,-1.2478 11.3915,-3.5756 3.3426,-2.3277 5.8919,-5.6236 7.3048,-9.444 -3.6324,4.2467 -10.648,7.1224 -18.6963,7.1224 z" 630 | style="mix-blend-mode:normal;fill:#f37726" /> 631 | </g> 632 | </g> 633 | <g 634 | id="g2550" 635 | style="mix-blend-mode:normal" 636 | figma:type="group"> 637 | <g 638 | id="path18 fill" 639 | style="mix-blend-mode:normal" 640 | figma:type="vector"> 641 | <path 642 | id="use2547" 643 | d="m 1661.9062,2463.7773 c 8.0127,0 15.055,2.8756 18.6963,7.1223 -1.4129,-3.8204 -3.9622,-7.1163 -7.3048,-9.444 -3.3427,-2.3277 -7.3182,-3.5756 -11.3915,-3.5756 -4.0732,0 -8.0488,1.2479 -11.3914,3.5756 -3.3426,2.3277 -5.892,5.6236 -7.3048,9.444 3.6413,-4.2556 10.648,-7.1223 18.6962,-7.1223 z" 644 | style="mix-blend-mode:normal;fill:#f37726" /> 645 | </g> 646 | </g> 647 | <g 648 | id="g2555" 649 | style="mix-blend-mode:normal" 650 | figma:type="group"> 651 | <g 652 | id="path19 fill" 653 | style="mix-blend-mode:normal" 654 | figma:type="vector"> 655 | <path 656 | id="use2552" 657 | d="m 1650.8758,2499.6566 c 0.047,0.7533 -0.1314,1.5036 -0.5123,2.1553 -0.381,0.6516 -0.9473,1.1751 -1.6268,1.5037 -0.6796,0.3286 -1.4415,0.4475 -2.1889,0.3416 -0.7473,-0.106 -1.4462,-0.4321 -2.0076,-0.9367 -0.5614,-0.5046 -0.9598,-1.1649 -1.1446,-1.8967 -0.1847,-0.7319 -0.1474,-1.5022 0.1072,-2.2128 0.2546,-0.7106 0.715,-1.3293 1.3224,-1.7773 0.6075,-0.448 1.3347,-0.705 2.0887,-0.7383 0.494,-0.026 0.9884,0.045 1.4549,0.2094 0.4665,0.1647 0.8959,0.4197 1.2638,0.7504 0.368,0.3307 0.6671,0.7306 0.8804,1.177 0.2133,0.4464 0.3366,0.9304 0.3628,1.4244 z" 658 | style="mix-blend-mode:normal;fill:#9e9e9e" /> 659 | </g> 660 | </g> 661 | <g 662 | id="g2560" 663 | style="mix-blend-mode:normal" 664 | figma:type="group"> 665 | <g 666 | id="path20 fill" 667 | style="mix-blend-mode:normal" 668 | figma:type="vector"> 669 | <path 670 | id="use2557" 671 | d="m 1644.1206,2462.8094 c -0.4317,0.012 -0.8574,-0.1041 -1.2234,-0.3334 -0.366,-0.2293 -0.656,-0.5618 -0.8336,-0.9555 -0.1775,-0.3937 -0.2347,-0.8312 -0.1643,-1.2573 0.07,-0.4261 0.2652,-0.822 0.5599,-1.1377 0.2948,-0.3157 0.6763,-0.5372 1.0966,-0.6366 0.4203,-0.099 0.8606,-0.072 1.2656,0.078 0.405,0.1501 0.7566,0.4166 1.0105,0.766 0.2539,0.3494 0.3988,0.7661 0.4165,1.1977 0.017,0.5835 -0.1971,1.1501 -0.5955,1.5768 -0.3984,0.4268 -0.949,0.6791 -1.5323,0.7023 z" 672 | style="mix-blend-mode:normal;fill:#616262" /> 673 | </g> 674 | </g> 675 | </g> 676 | </g> 677 | </g> 678 | </g> 679 | </svg> 680 | ``` -------------------------------------------------------------------------------- /jupyter_mcp_server/server.py: -------------------------------------------------------------------------------- ```python 1 | # Copyright (c) 2023-2024 Datalayer, Inc. 2 | # 3 | # BSD 3-Clause License 4 | 5 | import logging 6 | import click 7 | import httpx 8 | import uvicorn 9 | from typing import Union, Optional 10 | from fastapi import Request 11 | from jupyter_kernel_client import KernelClient 12 | from jupyter_server_api import JupyterServerClient 13 | from mcp.server import FastMCP 14 | from starlette.applications import Starlette 15 | from starlette.responses import JSONResponse 16 | from starlette.middleware.cors import CORSMiddleware 17 | 18 | from jupyter_mcp_server.models import DocumentRuntime 19 | from jupyter_mcp_server.utils import ( 20 | extract_output, 21 | safe_extract_outputs, 22 | create_kernel, 23 | start_kernel, 24 | ensure_kernel_alive, 25 | execute_cell_with_timeout, 26 | execute_cell_with_forced_sync, 27 | is_kernel_busy, 28 | wait_for_kernel_idle, 29 | safe_notebook_operation, 30 | list_files_recursively, 31 | ) 32 | from jupyter_mcp_server.config import get_config, set_config 33 | from jupyter_mcp_server.notebook_manager import NotebookManager 34 | from jupyter_mcp_server.enroll import auto_enroll_document 35 | from jupyter_mcp_server.tools import ( 36 | # Tool infrastructure 37 | ServerMode, 38 | # Notebook Management 39 | ListNotebooksTool, 40 | UseNotebookTool, 41 | RestartNotebookTool, 42 | UnuseNotebookTool, 43 | # Cell Reading 44 | ReadCellsTool, 45 | ListCellsTool, 46 | ReadCellTool, 47 | # Cell Writing 48 | InsertCellTool, 49 | InsertExecuteCodeCellTool, 50 | OverwriteCellSourceTool, 51 | DeleteCellTool, 52 | # Cell Execution 53 | ExecuteCellTool, 54 | # Other Tools 55 | AssignKernelToNotebookTool, 56 | ExecuteIpythonTool, 57 | ListFilesTool, 58 | ListKernelsTool, 59 | ) 60 | from typing import Literal, Union 61 | from mcp.types import ImageContent 62 | 63 | 64 | ############################################################################### 65 | 66 | 67 | logger = logging.getLogger(__name__) 68 | 69 | 70 | ############################################################################### 71 | 72 | class FastMCPWithCORS(FastMCP): 73 | def streamable_http_app(self) -> Starlette: 74 | """Return StreamableHTTP server app with CORS middleware 75 | See: https://github.com/modelcontextprotocol/python-sdk/issues/187 76 | """ 77 | # Get the original Starlette app 78 | app = super().streamable_http_app() 79 | 80 | # Add CORS middleware 81 | app.add_middleware( 82 | CORSMiddleware, 83 | allow_origins=["*"], # In production, should set specific domains 84 | allow_credentials=True, 85 | allow_methods=["*"], 86 | allow_headers=["*"], 87 | ) 88 | return app 89 | 90 | def sse_app(self, mount_path: str | None = None) -> Starlette: 91 | """Return SSE server app with CORS middleware""" 92 | # Get the original Starlette app 93 | app = super().sse_app(mount_path) 94 | # Add CORS middleware 95 | app.add_middleware( 96 | CORSMiddleware, 97 | allow_origins=["*"], # In production, should set specific domains 98 | allow_credentials=True, 99 | allow_methods=["*"], 100 | allow_headers=["*"], 101 | ) 102 | return app 103 | 104 | 105 | ############################################################################### 106 | 107 | 108 | mcp = FastMCPWithCORS(name="Jupyter MCP Server", json_response=False, stateless_http=True) 109 | 110 | # Initialize the unified notebook manager 111 | notebook_manager = NotebookManager() 112 | 113 | # Initialize all tool instances (no arguments needed - tools receive dependencies via execute()) 114 | # Notebook Management Tools 115 | list_notebook_tool = ListNotebooksTool() 116 | use_notebook_tool = UseNotebookTool() 117 | restart_notebook_tool = RestartNotebookTool() 118 | unuse_notebook_tool = UnuseNotebookTool() 119 | 120 | # Cell Reading Tools 121 | read_cells_tool = ReadCellsTool() 122 | list_cells_tool = ListCellsTool() 123 | read_cell_tool = ReadCellTool() 124 | 125 | # Cell Writing Tools 126 | insert_cell_tool = InsertCellTool() 127 | insert_execute_code_cell_tool = InsertExecuteCodeCellTool() 128 | overwrite_cell_source_tool = OverwriteCellSourceTool() 129 | delete_cell_tool = DeleteCellTool() 130 | 131 | # Cell Execution Tools 132 | execute_cell_tool = ExecuteCellTool() 133 | 134 | # Other Tools 135 | assign_kernel_to_notebook_tool = AssignKernelToNotebookTool() 136 | execute_ipython_tool = ExecuteIpythonTool() 137 | list_files_tool = ListFilesTool() 138 | list_kernel_tool = ListKernelsTool() 139 | 140 | 141 | ############################################################################### 142 | 143 | 144 | class ServerContext: 145 | """Singleton to cache server mode and context managers.""" 146 | _instance = None 147 | _mode = None 148 | _contents_manager = None 149 | _kernel_manager = None 150 | _kernel_spec_manager = None 151 | _session_manager = None 152 | _server_client = None 153 | _kernel_client = None 154 | _initialized = False 155 | 156 | @classmethod 157 | def get_instance(cls): 158 | if cls._instance is None: 159 | cls._instance = cls() 160 | return cls._instance 161 | 162 | @classmethod 163 | def reset(cls): 164 | """Reset the singleton instance. Use this when config changes.""" 165 | if cls._instance is not None: 166 | cls._instance._initialized = False 167 | cls._instance._mode = None 168 | cls._instance._contents_manager = None 169 | cls._instance._kernel_manager = None 170 | cls._instance._kernel_spec_manager = None 171 | cls._instance._session_manager = None 172 | cls._instance._server_client = None 173 | cls._instance._kernel_client = None 174 | 175 | def initialize(self): 176 | """Initialize context once.""" 177 | if self._initialized: 178 | return 179 | 180 | try: 181 | from jupyter_mcp_server.jupyter_extension.context import get_server_context 182 | context = get_server_context() 183 | 184 | if context.is_local_document() and context.get_contents_manager() is not None: 185 | self._mode = ServerMode.JUPYTER_SERVER 186 | self._contents_manager = context.get_contents_manager() 187 | self._kernel_manager = context.get_kernel_manager() 188 | self._kernel_spec_manager = context.get_kernel_spec_manager() if hasattr(context, 'get_kernel_spec_manager') else None 189 | self._session_manager = context.get_session_manager() if hasattr(context, 'get_session_manager') else None 190 | else: 191 | self._mode = ServerMode.MCP_SERVER 192 | # Initialize HTTP clients for MCP_SERVER mode 193 | config = get_config() 194 | 195 | # Validate that runtime_url is set and not None/empty 196 | # Note: String "None" values should have been normalized by start_command() 197 | runtime_url = config.runtime_url 198 | if not runtime_url or runtime_url in ("None", "none", "null", ""): 199 | raise ValueError( 200 | f"runtime_url is not configured (current value: {repr(runtime_url)}). " 201 | "Please check:\n" 202 | "1. RUNTIME_URL environment variable is set correctly (not the string 'None')\n" 203 | "2. --runtime-url argument is provided when starting the server\n" 204 | "3. The MCP client configuration passes runtime_url correctly" 205 | ) 206 | 207 | logger.info(f"Initializing MCP_SERVER mode with runtime_url: {runtime_url}") 208 | self._server_client = JupyterServerClient(base_url=runtime_url, token=config.runtime_token) 209 | # kernel_client will be created lazily when needed 210 | except (ImportError, Exception) as e: 211 | # If not in Jupyter context, use MCP_SERVER mode 212 | if not isinstance(e, ValueError): 213 | self._mode = ServerMode.MCP_SERVER 214 | # Initialize HTTP clients for MCP_SERVER mode 215 | config = get_config() 216 | 217 | # Validate that runtime_url is set and not None/empty 218 | # Note: String "None" values should have been normalized by start_command() 219 | runtime_url = config.runtime_url 220 | if not runtime_url or runtime_url in ("None", "none", "null", ""): 221 | raise ValueError( 222 | f"runtime_url is not configured (current value: {repr(runtime_url)}). " 223 | "Please check:\n" 224 | "1. RUNTIME_URL environment variable is set correctly (not the string 'None')\n" 225 | "2. --runtime-url argument is provided when starting the server\n" 226 | "3. The MCP client configuration passes runtime_url correctly" 227 | ) 228 | 229 | logger.info(f"Initializing MCP_SERVER mode with runtime_url: {runtime_url}") 230 | self._server_client = JupyterServerClient(base_url=runtime_url, token=config.runtime_token) 231 | else: 232 | raise 233 | 234 | self._initialized = True 235 | logger.info(f"Server mode initialized: {self._mode}") 236 | 237 | @property 238 | def mode(self): 239 | if not self._initialized: 240 | self.initialize() 241 | return self._mode 242 | 243 | @property 244 | def contents_manager(self): 245 | if not self._initialized: 246 | self.initialize() 247 | return self._contents_manager 248 | 249 | @property 250 | def kernel_manager(self): 251 | if not self._initialized: 252 | self.initialize() 253 | return self._kernel_manager 254 | 255 | @property 256 | def kernel_spec_manager(self): 257 | if not self._initialized: 258 | self.initialize() 259 | return self._kernel_spec_manager 260 | 261 | @property 262 | def session_manager(self): 263 | if not self._initialized: 264 | self.initialize() 265 | return self._session_manager 266 | 267 | @property 268 | def server_client(self): 269 | if not self._initialized: 270 | self.initialize() 271 | return self._server_client 272 | 273 | @property 274 | def kernel_client(self): 275 | if not self._initialized: 276 | self.initialize() 277 | return self._kernel_client 278 | 279 | 280 | # Initialize server context singleton 281 | server_context = ServerContext.get_instance() 282 | 283 | 284 | ############################################################################### 285 | 286 | 287 | def __create_kernel() -> KernelClient: 288 | """Create a new kernel instance using current configuration.""" 289 | config = get_config() 290 | return create_kernel(config, logger) 291 | 292 | 293 | def __start_kernel(): 294 | """Start the Jupyter kernel with error handling (for backward compatibility).""" 295 | config = get_config() 296 | start_kernel(notebook_manager, config, logger) 297 | 298 | 299 | async def __auto_enroll_document(): 300 | """Wrapper for auto_enroll_document that uses server context.""" 301 | await auto_enroll_document( 302 | config=get_config(), 303 | notebook_manager=notebook_manager, 304 | use_notebook_tool=use_notebook_tool, 305 | server_context=server_context, 306 | ) 307 | 308 | 309 | def __ensure_kernel_alive() -> KernelClient: 310 | """Ensure kernel is running, restart if needed.""" 311 | current_notebook = notebook_manager.get_current_notebook() or "default" 312 | return ensure_kernel_alive(notebook_manager, current_notebook, __create_kernel) 313 | 314 | 315 | async def __execute_cell_with_timeout(notebook, cell_index, kernel, timeout_seconds=300): 316 | """Execute a cell with timeout and real-time output sync.""" 317 | return await execute_cell_with_timeout(notebook, cell_index, kernel, timeout_seconds, logger) 318 | 319 | 320 | async def __execute_cell_with_forced_sync(notebook, cell_index, kernel, timeout_seconds=300): 321 | """Execute cell with forced real-time synchronization.""" 322 | return await execute_cell_with_forced_sync(notebook, cell_index, kernel, timeout_seconds, logger) 323 | 324 | 325 | def __is_kernel_busy(kernel): 326 | """Check if kernel is currently executing something.""" 327 | return is_kernel_busy(kernel) 328 | 329 | 330 | async def __wait_for_kernel_idle(kernel, max_wait_seconds=60): 331 | """Wait for kernel to become idle before proceeding.""" 332 | return await wait_for_kernel_idle(kernel, logger, max_wait_seconds) 333 | 334 | 335 | async def __safe_notebook_operation(operation_func, max_retries=3): 336 | """Safely execute notebook operations with connection recovery.""" 337 | return await safe_notebook_operation(operation_func, logger, max_retries) 338 | 339 | 340 | def _list_files_recursively(server_client, current_path="", current_depth=0, files=None, max_depth=3): 341 | """Recursively list all files and directories in the Jupyter server.""" 342 | return list_files_recursively(server_client, current_path, current_depth, files, max_depth) 343 | 344 | 345 | ############################################################################### 346 | # Custom Routes. 347 | 348 | 349 | @mcp.custom_route("/api/connect", ["PUT"]) 350 | async def connect(request: Request): 351 | """Connect to a document and a runtime from the Jupyter MCP server.""" 352 | 353 | data = await request.json() 354 | 355 | # Log the received data for diagnostics 356 | # Note: set_config() will automatically normalize string "None" values 357 | logger.info( 358 | f"Connect endpoint received - runtime_url: {repr(data.get('runtime_url'))}, " 359 | f"document_url: {repr(data.get('document_url'))}, " 360 | f"provider: {data.get('provider')}" 361 | ) 362 | 363 | document_runtime = DocumentRuntime(**data) 364 | 365 | # Clean up existing default notebook if any 366 | if "default" in notebook_manager: 367 | try: 368 | notebook_manager.remove_notebook("default") 369 | except Exception as e: 370 | logger.warning(f"Error stopping existing notebook during connect: {e}") 371 | 372 | # Update configuration with new values 373 | # String "None" values will be automatically normalized by set_config() 374 | set_config( 375 | provider=document_runtime.provider, 376 | runtime_url=document_runtime.runtime_url, 377 | runtime_id=document_runtime.runtime_id, 378 | runtime_token=document_runtime.runtime_token, 379 | document_url=document_runtime.document_url, 380 | document_id=document_runtime.document_id, 381 | document_token=document_runtime.document_token 382 | ) 383 | 384 | # Reset ServerContext to pick up new configuration 385 | ServerContext.reset() 386 | 387 | try: 388 | __start_kernel() 389 | return JSONResponse({"success": True}) 390 | except Exception as e: 391 | logger.error(f"Failed to connect: {e}") 392 | return JSONResponse({"success": False, "error": str(e)}, status_code=500) 393 | 394 | 395 | @mcp.custom_route("/api/stop", ["DELETE"]) 396 | async def stop(request: Request): 397 | try: 398 | current_notebook = notebook_manager.get_current_notebook() or "default" 399 | if current_notebook in notebook_manager: 400 | notebook_manager.remove_notebook(current_notebook) 401 | return JSONResponse({"success": True}) 402 | except Exception as e: 403 | logger.error(f"Error stopping notebook: {e}") 404 | return JSONResponse({"success": False, "error": str(e)}, status_code=500) 405 | 406 | 407 | @mcp.custom_route("/api/healthz", ["GET"]) 408 | async def health_check(request: Request): 409 | """Custom health check endpoint""" 410 | kernel_status = "unknown" 411 | try: 412 | current_notebook = notebook_manager.get_current_notebook() or "default" 413 | kernel = notebook_manager.get_kernel(current_notebook) 414 | if kernel: 415 | kernel_status = "alive" if hasattr(kernel, 'is_alive') and kernel.is_alive() else "dead" 416 | else: 417 | kernel_status = "not_initialized" 418 | except Exception: 419 | kernel_status = "error" 420 | return JSONResponse( 421 | { 422 | "success": True, 423 | "service": "jupyter-mcp-server", 424 | "message": "Jupyter MCP Server is running.", 425 | "status": "healthy", 426 | "kernel_status": kernel_status, 427 | } 428 | ) 429 | 430 | 431 | ############################################################################### 432 | # Tools. 433 | ############################################################################### 434 | 435 | ############################################################################### 436 | # Multi-Notebook Management Tools. 437 | 438 | 439 | @mcp.tool() 440 | async def use_notebook( 441 | notebook_name: str, 442 | notebook_path: Optional[str] = None, 443 | mode: Literal["connect", "create"] = "connect", 444 | kernel_id: Optional[str] = None, 445 | ) -> str: 446 | """Use a notebook file (connect to existing or create new, or switch to already-connected notebook). 447 | 448 | Args: 449 | notebook_name: Unique identifier for the notebook 450 | notebook_path: Path to the notebook file, relative to the Jupyter server root (e.g. "notebook.ipynb"). 451 | Optional - if not provided, switches to an already-connected notebook with the given name. 452 | mode: "connect" to connect to existing, "create" to create new 453 | kernel_id: Specific kernel ID to use (optional, will create new if not provided) 454 | 455 | Returns: 456 | str: Success message with notebook information 457 | """ 458 | config = get_config() 459 | return await __safe_notebook_operation( 460 | lambda: use_notebook_tool.execute( 461 | mode=server_context.mode, 462 | server_client=server_context.server_client, 463 | notebook_name=notebook_name, 464 | notebook_path=notebook_path, 465 | use_mode=mode, 466 | kernel_id=kernel_id, 467 | ensure_kernel_alive_fn=__ensure_kernel_alive, 468 | contents_manager=server_context.contents_manager, 469 | kernel_manager=server_context.kernel_manager, 470 | session_manager=server_context.session_manager, 471 | notebook_manager=notebook_manager, 472 | runtime_url=config.runtime_url if config.runtime_url != "local" else None, 473 | runtime_token=config.runtime_token, 474 | ) 475 | ) 476 | 477 | 478 | @mcp.tool() 479 | async def list_notebooks() -> str: 480 | """List all notebooks in the Jupyter server (including subdirectories) and show which ones are managed. 481 | 482 | To interact with a notebook, it has to be "managed". If a notebook is not managed, you can use it with the `use_notebook` tool. 483 | 484 | Returns: 485 | str: TSV formatted table with notebook information including management status 486 | """ 487 | return await list_notebook_tool.execute( 488 | mode=server_context.mode, 489 | server_client=server_context.server_client, 490 | contents_manager=server_context.contents_manager, 491 | kernel_manager=server_context.kernel_manager, 492 | notebook_manager=notebook_manager, 493 | ) 494 | 495 | 496 | @mcp.tool() 497 | async def restart_notebook(notebook_name: str) -> str: 498 | """Restart the kernel for a specific notebook. 499 | 500 | Args: 501 | notebook_name: Notebook identifier to restart 502 | 503 | Returns: 504 | str: Success message 505 | """ 506 | return await restart_notebook_tool.execute( 507 | mode=server_context.mode, 508 | notebook_name=notebook_name, 509 | notebook_manager=notebook_manager, 510 | kernel_manager=server_context.kernel_manager, 511 | ) 512 | 513 | 514 | @mcp.tool() 515 | async def unuse_notebook(notebook_name: str) -> str: 516 | """Unuse from a specific notebook and release its resources. 517 | 518 | Args: 519 | notebook_name: Notebook identifier to disconnect 520 | 521 | Returns: 522 | str: Success message 523 | """ 524 | return await unuse_notebook_tool.execute( 525 | mode=server_context.mode, 526 | notebook_name=notebook_name, 527 | notebook_manager=notebook_manager, 528 | kernel_manager=server_context.kernel_manager, 529 | ) 530 | 531 | 532 | ############################################################################### 533 | # Cell Tools. 534 | 535 | @mcp.tool() 536 | async def insert_cell( 537 | cell_index: int, 538 | cell_type: Literal["code", "markdown"], 539 | cell_source: str, 540 | ) -> str: 541 | """Insert a cell to specified position. 542 | 543 | Args: 544 | cell_index: target index for insertion (0-based). Use -1 to append at end. 545 | cell_type: Type of cell to insert ("code" or "markdown") 546 | cell_source: Source content for the cell 547 | 548 | Returns: 549 | str: Success message and the structure of its surrounding cells (up to 5 cells above and 5 cells below) 550 | """ 551 | return await __safe_notebook_operation( 552 | lambda: insert_cell_tool.execute( 553 | mode=server_context.mode, 554 | server_client=server_context.server_client, 555 | contents_manager=server_context.contents_manager, 556 | kernel_manager=server_context.kernel_manager, 557 | notebook_manager=notebook_manager, 558 | cell_index=cell_index, 559 | cell_source=cell_source, 560 | cell_type=cell_type, 561 | ) 562 | ) 563 | 564 | 565 | @mcp.tool() 566 | async def insert_execute_code_cell(cell_index: int, cell_source: str) -> list[Union[str, ImageContent]]: 567 | """Insert and execute a code cell in a Jupyter notebook. 568 | 569 | Args: 570 | cell_index: Index of the cell to insert (0-based). Use -1 to append at end and execute. 571 | cell_source: Code source 572 | 573 | Returns: 574 | list[Union[str, ImageContent]]: List of outputs from the executed cell 575 | """ 576 | return await __safe_notebook_operation( 577 | lambda: insert_execute_code_cell_tool.execute( 578 | mode=server_context.mode, 579 | server_client=server_context.server_client, 580 | contents_manager=server_context.contents_manager, 581 | kernel_manager=server_context.kernel_manager, 582 | notebook_manager=notebook_manager, 583 | cell_index=cell_index, 584 | cell_source=cell_source, 585 | ensure_kernel_alive=__ensure_kernel_alive, 586 | ) 587 | ) 588 | 589 | 590 | @mcp.tool() 591 | async def overwrite_cell_source(cell_index: int, cell_source: str) -> str: 592 | """Overwrite the source of an existing cell. 593 | Note this does not execute the modified cell by itself. 594 | 595 | Args: 596 | cell_index: Index of the cell to overwrite (0-based) 597 | cell_source: New cell source - must match existing cell type 598 | 599 | Returns: 600 | str: Success message with diff showing changes made 601 | """ 602 | return await __safe_notebook_operation( 603 | lambda: overwrite_cell_source_tool.execute( 604 | mode=server_context.mode, 605 | server_client=server_context.server_client, 606 | contents_manager=server_context.contents_manager, 607 | kernel_manager=server_context.kernel_manager, 608 | notebook_manager=notebook_manager, 609 | cell_index=cell_index, 610 | cell_source=cell_source, 611 | ) 612 | ) 613 | 614 | @mcp.tool() 615 | async def execute_cell(cell_index: int, timeout_seconds: int = 300, stream: bool = False, progress_interval: int = 5) -> list[Union[str, ImageContent]]: 616 | """Execute a cell with configurable timeout and optional streaming progress updates. 617 | 618 | Args: 619 | cell_index: Index of the cell to execute (0-based) 620 | timeout_seconds: Maximum time to wait for execution (default: 300s) 621 | stream: Enable streaming progress updates for long-running cells (default: False) 622 | progress_interval: Seconds between progress updates when stream=True (default: 5s) 623 | Returns: 624 | list[Union[str, ImageContent]]: List of outputs from the executed cell 625 | """ 626 | return await __safe_notebook_operation( 627 | lambda: execute_cell_tool.execute( 628 | mode=server_context.mode, 629 | server_client=server_context.server_client, 630 | contents_manager=server_context.contents_manager, 631 | kernel_manager=server_context.kernel_manager, 632 | notebook_manager=notebook_manager, 633 | cell_index=cell_index, 634 | timeout_seconds=timeout_seconds, 635 | stream=stream, 636 | progress_interval=progress_interval, 637 | ensure_kernel_alive_fn=__ensure_kernel_alive, 638 | wait_for_kernel_idle_fn=__wait_for_kernel_idle, 639 | safe_extract_outputs_fn=safe_extract_outputs, 640 | execute_cell_with_forced_sync_fn=__execute_cell_with_forced_sync, 641 | extract_output_fn=extract_output, 642 | ), 643 | max_retries=1 644 | ) 645 | 646 | 647 | @mcp.tool() 648 | async def read_cells() -> list[dict[str, Union[str, int, list[Union[str, ImageContent]]]]]: 649 | """Read all cells from the Jupyter notebook. 650 | Returns: 651 | list[dict]: List of cell information including index, type, source, 652 | and outputs (for code cells) 653 | """ 654 | return await __safe_notebook_operation( 655 | lambda: read_cells_tool.execute( 656 | mode=server_context.mode, 657 | server_client=server_context.server_client, 658 | contents_manager=server_context.contents_manager, 659 | notebook_manager=notebook_manager, 660 | ) 661 | ) 662 | 663 | 664 | @mcp.tool() 665 | async def list_cells() -> str: 666 | """List the basic information of all cells in the notebook. 667 | 668 | Returns a formatted table showing the index, type, execution count (for code cells), 669 | and first line of each cell. This provides a quick overview of the notebook structure 670 | and is useful for locating specific cells for operations like delete or insert. 671 | 672 | Returns: 673 | str: Formatted table with cell information (Index, Type, Count, First Line) 674 | """ 675 | return await __safe_notebook_operation( 676 | lambda: list_cells_tool.execute( 677 | mode=server_context.mode, 678 | server_client=server_context.server_client, 679 | contents_manager=server_context.contents_manager, 680 | notebook_manager=notebook_manager, 681 | ) 682 | ) 683 | 684 | 685 | @mcp.tool() 686 | async def read_cell(cell_index: int) -> dict[str, Union[str, int, list[Union[str, ImageContent]]]]: 687 | """Read a specific cell from the Jupyter notebook. 688 | Args: 689 | cell_index: Index of the cell to read (0-based) 690 | Returns: 691 | dict: Cell information including index, type, source, and outputs (for code cells) 692 | """ 693 | return await __safe_notebook_operation( 694 | lambda: read_cell_tool.execute( 695 | mode=server_context.mode, 696 | server_client=server_context.server_client, 697 | contents_manager=server_context.contents_manager, 698 | notebook_manager=notebook_manager, 699 | cell_index=cell_index, 700 | ) 701 | ) 702 | 703 | @mcp.tool() 704 | async def delete_cell(cell_index: int) -> str: 705 | """Delete a specific cell from the Jupyter notebook. 706 | Args: 707 | cell_index: Index of the cell to delete (0-based) 708 | Returns: 709 | str: Success message 710 | """ 711 | return await __safe_notebook_operation( 712 | lambda: delete_cell_tool.execute( 713 | mode=server_context.mode, 714 | server_client=server_context.server_client, 715 | contents_manager=server_context.contents_manager, 716 | kernel_manager=server_context.kernel_manager, 717 | notebook_manager=notebook_manager, 718 | cell_index=cell_index, 719 | ) 720 | ) 721 | 722 | 723 | @mcp.tool() 724 | async def execute_ipython(code: str, timeout: int = 60) -> list[Union[str, ImageContent]]: 725 | """Execute IPython code directly in the kernel on the current active notebook. 726 | 727 | This powerful tool supports: 728 | 1. Magic commands (e.g., %timeit, %who, %load, %run, %matplotlib) 729 | 2. Shell commands (e.g., !pip install, !ls, !cat) 730 | 3. Python code (e.g., print(df.head()), df.info()) 731 | 732 | Use cases: 733 | - Performance profiling and debugging 734 | - Environment exploration and package management 735 | - Variable inspection and data analysis 736 | - File system operations on Jupyter server 737 | - Temporary calculations and quick tests 738 | 739 | Args: 740 | code: IPython code to execute (supports magic commands, shell commands with !, and Python code) 741 | timeout: Execution timeout in seconds (default: 60s) 742 | Returns: 743 | List of outputs from the executed code 744 | """ 745 | # Get kernel_id for JUPYTER_SERVER mode 746 | # Let the tool handle getting kernel_id via get_current_notebook_context() 747 | kernel_id = None 748 | if server_context.mode == ServerMode.JUPYTER_SERVER: 749 | current_notebook = notebook_manager.get_current_notebook() or "default" 750 | kernel_id = notebook_manager.get_kernel_id(current_notebook) 751 | # Note: kernel_id might be None here if notebook not in manager, 752 | # but the tool will fall back to config values via get_current_notebook_context() 753 | 754 | return await __safe_notebook_operation( 755 | lambda: execute_ipython_tool.execute( 756 | mode=server_context.mode, 757 | server_client=server_context.server_client, 758 | kernel_manager=server_context.kernel_manager, 759 | notebook_manager=notebook_manager, 760 | code=code, 761 | timeout=timeout, 762 | kernel_id=kernel_id, 763 | ensure_kernel_alive_fn=__ensure_kernel_alive, 764 | wait_for_kernel_idle_fn=__wait_for_kernel_idle, 765 | safe_extract_outputs_fn=safe_extract_outputs, 766 | ), 767 | max_retries=1 768 | ) 769 | 770 | 771 | @mcp.tool() 772 | async def list_files(path: str = "", max_depth: int = 3) -> str: 773 | """List all files and directories in the Jupyter server's file system. 774 | 775 | This tool recursively lists files and directories from the Jupyter server's content API, 776 | showing the complete file structure including notebooks, data files, scripts, and directories. 777 | 778 | Args: 779 | path: The starting path to list from (empty string means root directory) 780 | max_depth: Maximum depth to recurse into subdirectories (default: 3) 781 | 782 | Returns: 783 | str: Tab-separated table with columns: Path, Type, Size, Last_Modified 784 | """ 785 | return await __safe_notebook_operation( 786 | lambda: list_files_tool.execute( 787 | mode=server_context.mode, 788 | server_client=server_context.server_client, 789 | contents_manager=server_context.contents_manager, 790 | path=path, 791 | max_depth=max_depth, 792 | list_files_recursively_fn=_list_files_recursively, 793 | ) 794 | ) 795 | 796 | 797 | @mcp.tool() 798 | async def list_kernels() -> str: 799 | """List all available kernels in the Jupyter server. 800 | 801 | This tool shows all running and available kernel sessions on the Jupyter server, 802 | including their IDs, names, states, connection information, and kernel specifications. 803 | Useful for monitoring kernel resources and identifying specific kernels for connection. 804 | 805 | Returns: 806 | str: Tab-separated table with columns: ID, Name, Display_Name, Language, State, Connections, Last_Activity, Environment 807 | """ 808 | return await __safe_notebook_operation( 809 | lambda: list_kernel_tool.execute( 810 | mode=server_context.mode, 811 | server_client=server_context.server_client, 812 | kernel_manager=server_context.kernel_manager, 813 | kernel_spec_manager=server_context.kernel_spec_manager, 814 | ) 815 | ) 816 | 817 | 818 | @mcp.tool() 819 | async def assign_kernel_to_notebook( 820 | notebook_path: str, 821 | kernel_id: str, 822 | session_name: str = None 823 | ) -> str: 824 | """Assign a kernel to a notebook by creating a Jupyter session. 825 | 826 | This creates a Jupyter server session that connects a notebook file to a kernel, 827 | enabling code execution in the notebook. Sessions are the mechanism Jupyter uses 828 | to maintain the relationship between notebooks and their kernels. 829 | 830 | Args: 831 | notebook_path: Path to the notebook file, relative to the Jupyter server root (e.g. "notebook.ipynb") 832 | kernel_id: ID of the kernel to assign to the notebook 833 | session_name: Optional name for the session (defaults to notebook path) 834 | 835 | Returns: 836 | str: Success message with session information including session ID 837 | """ 838 | return await __safe_notebook_operation( 839 | lambda: assign_kernel_to_notebook_tool.execute( 840 | mode=server_context.mode, 841 | server_client=server_context.server_client, 842 | contents_manager=server_context.contents_manager, 843 | session_manager=server_context.session_manager, 844 | kernel_manager=server_context.kernel_manager, 845 | notebook_path=notebook_path, 846 | kernel_id=kernel_id, 847 | session_name=session_name, 848 | ) 849 | ) 850 | 851 | 852 | ############################################################################### 853 | # Helper Functions for Extension. 854 | 855 | 856 | async def get_registered_tools(): 857 | """ 858 | Get list of all registered MCP tools with their metadata. 859 | 860 | This function is used by the Jupyter extension to dynamically expose 861 | the tool registry without hardcoding tool names and parameters. 862 | 863 | Returns: 864 | list: List of tool dictionaries with name, description, and inputSchema 865 | """ 866 | # Use FastMCP's list_tools method which returns Tool objects 867 | tools_list = await mcp.list_tools() 868 | 869 | tools = [] 870 | for tool in tools_list: 871 | tool_dict = { 872 | "name": tool.name, 873 | "description": tool.description, 874 | } 875 | 876 | # Extract parameter names from inputSchema 877 | if hasattr(tool, 'inputSchema') and tool.inputSchema: 878 | input_schema = tool.inputSchema 879 | if 'properties' in input_schema: 880 | tool_dict["parameters"] = list(input_schema['properties'].keys()) 881 | else: 882 | tool_dict["parameters"] = [] 883 | 884 | # Include full inputSchema for MCP protocol compatibility 885 | tool_dict["inputSchema"] = input_schema 886 | else: 887 | tool_dict["parameters"] = [] 888 | 889 | tools.append(tool_dict) 890 | 891 | return tools 892 | 893 | 894 | ############################################################################### 895 | # Commands. 896 | 897 | 898 | # Shared options decorator to reduce code duplication 899 | def _common_options(f): 900 | """Decorator that adds common start options to a command.""" 901 | options = [ 902 | click.option( 903 | "--provider", 904 | envvar="PROVIDER", 905 | type=click.Choice(["jupyter", "datalayer"]), 906 | default="jupyter", 907 | help="The provider to use for the document and runtime. Defaults to 'jupyter'.", 908 | ), 909 | click.option( 910 | "--runtime-url", 911 | envvar="RUNTIME_URL", 912 | type=click.STRING, 913 | default="http://localhost:8888", 914 | help="The runtime URL to use. For the jupyter provider, this is the Jupyter server URL. For the datalayer provider, this is the Datalayer runtime URL.", 915 | ), 916 | click.option( 917 | "--runtime-id", 918 | envvar="RUNTIME_ID", 919 | type=click.STRING, 920 | default=None, 921 | help="The kernel ID to use. If not provided, a new kernel should be started.", 922 | ), 923 | click.option( 924 | "--runtime-token", 925 | envvar="RUNTIME_TOKEN", 926 | type=click.STRING, 927 | default=None, 928 | help="The runtime token to use for authentication with the provider. If not provided, the provider should accept anonymous requests.", 929 | ), 930 | click.option( 931 | "--document-url", 932 | envvar="DOCUMENT_URL", 933 | type=click.STRING, 934 | default="http://localhost:8888", 935 | help="The document URL to use. For the jupyter provider, this is the Jupyter server URL. For the datalayer provider, this is the Datalayer document URL.", 936 | ), 937 | click.option( 938 | "--document-id", 939 | envvar="DOCUMENT_ID", 940 | type=click.STRING, 941 | default=None, 942 | help="The document id to use. For the jupyter provider, this is the notebook path. For the datalayer provider, this is the notebook path. Optional - if omitted, you can list and select notebooks interactively.", 943 | ), 944 | click.option( 945 | "--document-token", 946 | envvar="DOCUMENT_TOKEN", 947 | type=click.STRING, 948 | default=None, 949 | help="The document token to use for authentication with the provider. If not provided, the provider should accept anonymous requests.", 950 | ) 951 | ] 952 | # Apply decorators in reverse order 953 | for option in reversed(options): 954 | f = option(f) 955 | return f 956 | 957 | def _do_start( 958 | transport: str, 959 | start_new_runtime: bool, 960 | runtime_url: str, 961 | runtime_id: str, 962 | runtime_token: str, 963 | document_url: str, 964 | document_id: str, 965 | document_token: str, 966 | port: int, 967 | provider: str, 968 | ): 969 | """Internal function to execute the start logic.""" 970 | 971 | # Log the received configuration for diagnostics 972 | # Note: set_config() will automatically normalize string "None" values 973 | logger.info( 974 | f"Start command received - runtime_url: {repr(runtime_url)}, " 975 | f"document_url: {repr(document_url)}, provider: {provider}, " 976 | f"transport: {transport}" 977 | ) 978 | 979 | # Set configuration using the singleton 980 | # String "None" values will be automatically normalized by set_config() 981 | config = set_config( 982 | transport=transport, 983 | provider=provider, 984 | runtime_url=runtime_url, 985 | start_new_runtime=start_new_runtime, 986 | runtime_id=runtime_id, 987 | runtime_token=runtime_token, 988 | document_url=document_url, 989 | document_id=document_id, 990 | document_token=document_token, 991 | port=port 992 | ) 993 | 994 | # Reset ServerContext to pick up new configuration 995 | ServerContext.reset() 996 | 997 | # Determine startup behavior based on configuration 998 | if config.document_id: 999 | # If document_id is provided, auto-enroll the notebook 1000 | # Kernel creation depends on start_new_runtime and runtime_id flags 1001 | try: 1002 | import asyncio 1003 | # Run the async enrollment in the event loop 1004 | asyncio.run(__auto_enroll_document()) 1005 | except Exception as e: 1006 | logger.error(f"Failed to auto-enroll document '{config.document_id}': {e}") 1007 | # Fallback to legacy kernel-only mode if enrollment fails 1008 | if config.start_new_runtime or config.runtime_id: 1009 | try: 1010 | __start_kernel() 1011 | except Exception as e2: 1012 | logger.error(f"Failed to start kernel on startup: {e2}") 1013 | elif config.start_new_runtime or config.runtime_id: 1014 | # If no document_id but start_new_runtime/runtime_id is set, just create kernel 1015 | # This is for backward compatibility - kernel without managed notebook 1016 | try: 1017 | __start_kernel() 1018 | except Exception as e: 1019 | logger.error(f"Failed to start kernel on startup: {e}") 1020 | # else: No startup action - user must manually enroll notebooks or create kernels 1021 | 1022 | logger.info(f"Starting Jupyter MCP Server with transport: {transport}") 1023 | 1024 | if transport == "stdio": 1025 | mcp.run(transport="stdio") 1026 | elif transport == "streamable-http": 1027 | uvicorn.run(mcp.streamable_http_app, host="0.0.0.0", port=port) # noqa: S104 1028 | else: 1029 | raise Exception("Transport should be `stdio` or `streamable-http`.") 1030 | 1031 | 1032 | @click.group(invoke_without_command=True) 1033 | @_common_options 1034 | @click.option( 1035 | "--transport", 1036 | envvar="TRANSPORT", 1037 | type=click.Choice(["stdio", "streamable-http"]), 1038 | default="stdio", 1039 | help="The transport to use for the MCP server. Defaults to 'stdio'.", 1040 | ) 1041 | @click.option( 1042 | "--start-new-runtime", 1043 | envvar="START_NEW_RUNTIME", 1044 | type=click.BOOL, 1045 | default=True, 1046 | help="Start a new runtime or use an existing one.", 1047 | ) 1048 | @click.option( 1049 | "--port", 1050 | envvar="PORT", 1051 | type=click.INT, 1052 | default=4040, 1053 | help="The port to use for the Streamable HTTP transport. Ignored for stdio transport.", 1054 | ) 1055 | @click.pass_context 1056 | def server( 1057 | ctx, 1058 | transport: str, 1059 | start_new_runtime: bool, 1060 | runtime_url: str, 1061 | runtime_id: str, 1062 | runtime_token: str, 1063 | document_url: str, 1064 | document_id: str, 1065 | document_token: str, 1066 | port: int, 1067 | provider: str, 1068 | ): 1069 | """Manages Jupyter MCP Server. 1070 | 1071 | When invoked without subcommands, starts the MCP server directly. 1072 | This allows for quick startup with: uvx jupyter-mcp-server 1073 | 1074 | Subcommands (start, connect, stop) are still available for advanced use cases. 1075 | """ 1076 | # If a subcommand is invoked, let it handle the execution 1077 | if ctx.invoked_subcommand is not None: 1078 | return 1079 | 1080 | # No subcommand provided - execute the default start behavior 1081 | _do_start( 1082 | transport=transport, 1083 | start_new_runtime=start_new_runtime, 1084 | runtime_url=runtime_url, 1085 | runtime_id=runtime_id, 1086 | runtime_token=runtime_token, 1087 | document_url=document_url, 1088 | document_id=document_id, 1089 | document_token=document_token, 1090 | port=port, 1091 | provider=provider, 1092 | ) 1093 | 1094 | 1095 | @server.command("connect") 1096 | @_common_options 1097 | @click.option( 1098 | "--jupyter-mcp-server-url", 1099 | envvar="JUPYTER_MCP_SERVER_URL", 1100 | type=click.STRING, 1101 | default="http://localhost:4040", 1102 | help="The URL of the Jupyter MCP Server to connect to. Defaults to 'http://localhost:4040'.", 1103 | ) 1104 | def connect_command( 1105 | jupyter_mcp_server_url: str, 1106 | runtime_url: str, 1107 | runtime_id: str, 1108 | runtime_token: str, 1109 | document_url: str, 1110 | document_id: str, 1111 | document_token: str, 1112 | provider: str, 1113 | ): 1114 | """Command to connect a Jupyter MCP Server to a document and a runtime.""" 1115 | 1116 | # Set configuration using the singleton 1117 | set_config( 1118 | provider=provider, 1119 | runtime_url=runtime_url, 1120 | runtime_id=runtime_id, 1121 | runtime_token=runtime_token, 1122 | document_url=document_url, 1123 | document_id=document_id, 1124 | document_token=document_token 1125 | ) 1126 | 1127 | config = get_config() 1128 | 1129 | document_runtime = DocumentRuntime( 1130 | provider=config.provider, 1131 | runtime_url=config.runtime_url, 1132 | runtime_id=config.runtime_id, 1133 | runtime_token=config.runtime_token, 1134 | document_url=config.document_url, 1135 | document_id=config.document_id, 1136 | document_token=config.document_token, 1137 | ) 1138 | 1139 | r = httpx.put( 1140 | f"{jupyter_mcp_server_url}/api/connect", 1141 | headers={ 1142 | "Content-Type": "application/json", 1143 | "Accept": "application/json", 1144 | }, 1145 | content=document_runtime.model_dump_json(), 1146 | ) 1147 | r.raise_for_status() 1148 | 1149 | 1150 | @server.command("stop") 1151 | @click.option( 1152 | "--jupyter-mcp-server-url", 1153 | envvar="JUPYTER_MCP_SERVER_URL", 1154 | type=click.STRING, 1155 | default="http://localhost:4040", 1156 | help="The URL of the Jupyter MCP Server to stop. Defaults to 'http://localhost:4040'.", 1157 | ) 1158 | def stop_command(jupyter_mcp_server_url: str): 1159 | r = httpx.delete( 1160 | f"{jupyter_mcp_server_url}/api/stop", 1161 | ) 1162 | r.raise_for_status() 1163 | 1164 | 1165 | @server.command("start") 1166 | @_common_options 1167 | @click.option( 1168 | "--transport", 1169 | envvar="TRANSPORT", 1170 | type=click.Choice(["stdio", "streamable-http"]), 1171 | default="stdio", 1172 | help="The transport to use for the MCP server. Defaults to 'stdio'.", 1173 | ) 1174 | @click.option( 1175 | "--start-new-runtime", 1176 | envvar="START_NEW_RUNTIME", 1177 | type=click.BOOL, 1178 | default=True, 1179 | help="Start a new runtime or use an existing one.", 1180 | ) 1181 | @click.option( 1182 | "--port", 1183 | envvar="PORT", 1184 | type=click.INT, 1185 | default=4040, 1186 | help="The port to use for the Streamable HTTP transport. Ignored for stdio transport.", 1187 | ) 1188 | def start_command( 1189 | transport: str, 1190 | start_new_runtime: bool, 1191 | runtime_url: str, 1192 | runtime_id: str, 1193 | runtime_token: str, 1194 | document_url: str, 1195 | document_id: str, 1196 | document_token: str, 1197 | port: int, 1198 | provider: str, 1199 | ): 1200 | """Start the Jupyter MCP server with a transport.""" 1201 | _do_start( 1202 | transport=transport, 1203 | start_new_runtime=start_new_runtime, 1204 | runtime_url=runtime_url, 1205 | runtime_id=runtime_id, 1206 | runtime_token=runtime_token, 1207 | document_url=document_url, 1208 | document_id=document_id, 1209 | document_token=document_token, 1210 | port=port, 1211 | provider=provider, 1212 | ) 1213 | 1214 | 1215 | ############################################################################### 1216 | # Main. 1217 | 1218 | 1219 | if __name__ == "__main__": 1220 | start_command() ```