#
tokens: 31744/50000 2/133 files (page 4/5)
lines: off (toggle) GitHub
raw markdown copy
This is page 4 of 5. Use http://codebase.md/datalayer/jupyter-mcp-server?lines=false&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
│       ├── 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:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
  ~ Copyright (c) 2023-2024 Datalayer, Inc.
  ~
  ~ BSD 3-Clause License
-->

<svg
   xmlns:figma="http://www.figma.com/figma/ns"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   viewBox="0 0 335.67566 308.79167"
   version="1.1"
   id="svg1327"
   sodipodi:docname="2.svg"
   inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
   width="335.67566"
   height="308.79166">
  <metadata
     id="metadata1331">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title>Web_hosting_SVG</dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <sodipodi:namedview
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1"
     objecttolerance="10"
     gridtolerance="10"
     guidetolerance="10"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:window-width="1357"
     inkscape:window-height="701"
     id="namedview1329"
     showgrid="false"
     inkscape:zoom="0.98691435"
     inkscape:cx="142.90769"
     inkscape:cy="115.1505"
     inkscape:window-x="0"
     inkscape:window-y="25"
     inkscape:window-maximized="0"
     inkscape:current-layer="svg1327"
     inkscape:document-rotation="0"
     fit-margin-top="0"
     fit-margin-left="0"
     fit-margin-right="0"
     fit-margin-bottom="0" />
  <defs
     id="defs835">
    <style
       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>
  </defs>
  <title
     id="title837">Web_hosting_SVG</title>
  <polygon
     class="cls-1"
     points="249.36,272.19 251.14,273.21 107.37,356.22 106.48,354.68 "
     id="polygon839"
     transform="translate(0.00186273,-119.51243)" />
  <path
     class="cls-1"
     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"
     id="path1155" />
  <path
     class="cls-7"
     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"
     id="path1157" />
  <path
     class="cls-8"
     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"
     id="path1159" />
  <path
     class="cls-9"
     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"
     id="path1161" />
  <g
     id="_Группа_"
     data-name="&lt;Группа&gt;"
     transform="translate(0.00186273,-119.51243)">
    <path
       id="_Контур_"
       data-name="&lt;Контур&gt;"
       class="cls-10"
       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" />
    <path
       id="_Контур_2"
       data-name="&lt;Контур&gt;"
       class="cls-10"
       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" />
    <g
       id="_Группа_2"
       data-name="&lt;Группа&gt;">
      <path
         id="_Контур_3"
         data-name="&lt;Контур&gt;"
         class="cls-11"
         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" />
    </g>
    <g
       id="_Группа_3"
       data-name="&lt;Группа&gt;">
      <polygon
         id="_Контур_4"
         data-name="&lt;Контур&gt;"
         class="cls-12"
         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 " />
      <polygon
         id="_Контур_5"
         data-name="&lt;Контур&gt;"
         class="cls-10"
         points="66.33,328.67 56.48,322.98 51.08,326.1 60.93,331.79 " />
    </g>
    <path
       class="cls-5"
       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"
       id="path1170" />
    <path
       class="cls-5"
       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"
       id="path1172" />
    <path
       class="cls-5"
       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"
       id="path1174" />
    <path
       class="cls-5"
       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"
       id="path1176" />
    <path
       class="cls-5"
       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"
       id="path1178" />
    <path
       class="cls-5"
       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"
       id="path1180" />
    <path
       class="cls-5"
       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"
       id="path1182" />
    <path
       class="cls-5"
       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"
       id="path1184" />
    <path
       class="cls-5"
       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"
       id="path1186" />
    <path
       class="cls-5"
       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"
       id="path1188" />
    <path
       class="cls-5"
       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"
       id="path1190" />
    <path
       class="cls-5"
       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"
       id="path1192" />
    <path
       class="cls-5"
       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"
       id="path1194" />
    <path
       class="cls-5"
       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"
       id="path1196" />
    <path
       class="cls-5"
       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"
       id="path1198" />
    <path
       class="cls-5"
       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"
       id="path1200" />
    <path
       class="cls-5"
       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"
       id="path1202" />
    <path
       class="cls-5"
       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"
       id="path1204" />
    <path
       class="cls-5"
       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"
       id="path1206" />
    <path
       class="cls-5"
       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"
       id="path1208" />
    <path
       class="cls-5"
       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"
       id="path1210" />
    <path
       class="cls-5"
       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"
       id="path1212" />
    <path
       class="cls-5"
       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"
       id="path1214" />
    <path
       class="cls-5"
       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"
       id="path1216" />
    <path
       class="cls-5"
       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"
       id="path1218" />
    <path
       class="cls-5"
       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"
       id="path1220" />
    <path
       class="cls-5"
       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"
       id="path1222" />
    <path
       class="cls-5"
       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"
       id="path1224" />
    <path
       class="cls-5"
       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"
       id="path1226" />
    <path
       class="cls-5"
       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"
       id="path1228" />
    <path
       class="cls-5"
       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"
       id="path1230" />
    <path
       class="cls-5"
       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"
       id="path1232" />
    <path
       class="cls-5"
       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"
       id="path1234" />
    <path
       class="cls-5"
       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"
       id="path1236" />
    <path
       class="cls-5"
       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"
       id="path1238" />
    <path
       class="cls-5"
       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"
       id="path1240" />
    <path
       class="cls-5"
       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"
       id="path1242" />
    <path
       class="cls-5"
       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"
       id="path1244" />
    <path
       class="cls-5"
       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"
       id="path1246" />
    <path
       class="cls-5"
       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"
       id="path1248" />
    <path
       class="cls-5"
       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"
       id="path1250" />
    <path
       class="cls-5"
       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"
       id="path1252" />
    <path
       class="cls-5"
       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"
       id="path1254" />
    <path
       class="cls-5"
       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"
       id="path1256" />
    <path
       class="cls-5"
       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"
       id="path1258" />
    <path
       class="cls-5"
       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"
       id="path1260" />
    <path
       class="cls-5"
       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"
       id="path1262" />
    <path
       class="cls-5"
       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"
       id="path1264" />
    <path
       class="cls-5"
       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"
       id="path1266" />
    <path
       class="cls-5"
       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"
       id="path1268" />
    <path
       class="cls-5"
       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"
       id="path1270" />
    <path
       class="cls-5"
       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"
       id="path1272" />
    <path
       class="cls-5"
       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"
       id="path1274" />
    <path
       class="cls-5"
       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"
       id="path1276" />
    <path
       class="cls-5"
       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"
       id="path1278" />
    <path
       class="cls-5"
       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"
       id="path1280" />
    <path
       class="cls-5"
       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"
       id="path1282" />
    <path
       class="cls-5"
       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"
       id="path1284" />
    <path
       class="cls-5"
       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"
       id="path1286" />
    <path
       class="cls-5"
       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"
       id="path1288" />
    <path
       class="cls-5"
       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"
       id="path1290" />
    <path
       class="cls-5"
       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"
       id="path1292" />
    <path
       class="cls-5"
       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"
       id="path1294" />
    <path
       class="cls-5"
       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"
       id="path1296" />
  </g>
  <g
     id="_Группа_4"
     data-name="&lt;Группа&gt;"
     transform="translate(0.00186273,-119.51243)">
    <g
       id="_Группа_5"
       data-name="&lt;Группа&gt;">
      <path
         id="_Контур_6"
         data-name="&lt;Контур&gt;"
         class="cls-10"
         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" />
      <path
         id="_Контур_7"
         data-name="&lt;Контур&gt;"
         class="cls-10"
         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" />
      <path
         id="_Контур_8"
         data-name="&lt;Контур&gt;"
         class="cls-13"
         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" />
      <path
         id="_Контур_9"
         data-name="&lt;Контур&gt;"
         class="cls-11"
         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" />
      <polygon
         id="_Контур_10"
         data-name="&lt;Контур&gt;"
         class="cls-14"
         points="60.93,306.48 60.93,307.4 93.16,326.01 93.16,325.09 " />
    </g>
    <polygon
       id="_Контур_11"
       data-name="&lt;Контур&gt;"
       class="cls-15"
       points="92.12,323.11 92.12,300.34 62.16,283.04 62.16,305.86 " />
  </g>
  <g
     id="Men_2"
     transform="translate(0.00186273,-119.51243)">
    <path
       id="_Контур_12"
       data-name="&lt;Контур&gt;"
       class="cls-16"
       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" />
    <path
       id="_Контур_13"
       data-name="&lt;Контур&gt;"
       class="cls-17"
       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" />
    <path
       class="cls-18"
       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"
       id="path1309" />
    <path
       class="cls-19"
       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"
       id="path1311" />
    <path
       class="cls-18"
       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"
       id="path1313" />
    <path
       class="cls-19"
       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"
       id="path1315" />
    <path
       id="_Контур_14"
       data-name="&lt;Контур&gt;"
       class="cls-6"
       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" />
    <path
       id="_Контур_15"
       data-name="&lt;Контур&gt;"
       class="cls-16"
       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" />
    <path
       id="_Контур_16"
       data-name="&lt;Контур&gt;"
       class="cls-11"
       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" />
    <path
       id="_Контур_17"
       data-name="&lt;Контур&gt;"
       class="cls-16"
       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" />
    <path
       id="_Контур_18"
       data-name="&lt;Контур&gt;"
       class="cls-17"
       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" />
    <path
       class="cls-20"
       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"
       id="path1322" />
  </g>
  <g
     id="Canvas"
     transform="matrix(4.0495342,0,0,4.0495342,-6480.5841,-9936.3011)"
     figma:type="canvas">
    <g
       id="g2566"
       style="mix-blend-mode:normal"
       figma:type="group">
      <g
         id="g2564"
         style="mix-blend-mode:normal"
         figma:type="group">
        <g
           id="Group"
           style="mix-blend-mode:normal"
           figma:type="group">
          <g
             id="g"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path9 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2501"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2508"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path10 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2505"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2513"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path11 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2510"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2518"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path12 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2515"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2523"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path13 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2520"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2528"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path14 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2525"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
            <g
               id="g2533"
               style="mix-blend-mode:normal"
               figma:type="group">
              <g
                 id="path15 fill"
                 style="mix-blend-mode:normal"
                 figma:type="vector">
                <path
                   id="use2530"
                   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"
                   style="mix-blend-mode:normal;fill:#4e4e4e" />
              </g>
            </g>
          </g>
        </g>
        <g
           id="g2562"
           style="mix-blend-mode:normal"
           figma:type="group">
          <g
             id="g2540"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path16 fill"
               style="mix-blend-mode:normal"
               figma:type="vector">
              <path
                 id="use2537"
                 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"
                 style="mix-blend-mode:normal;fill:#767677" />
            </g>
          </g>
          <g
             id="g2545"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path17 fill"
               style="mix-blend-mode:normal"
               figma:type="vector">
              <path
                 id="use2542"
                 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"
                 style="mix-blend-mode:normal;fill:#f37726" />
            </g>
          </g>
          <g
             id="g2550"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path18 fill"
               style="mix-blend-mode:normal"
               figma:type="vector">
              <path
                 id="use2547"
                 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"
                 style="mix-blend-mode:normal;fill:#f37726" />
            </g>
          </g>
          <g
             id="g2555"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path19 fill"
               style="mix-blend-mode:normal"
               figma:type="vector">
              <path
                 id="use2552"
                 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"
                 style="mix-blend-mode:normal;fill:#9e9e9e" />
            </g>
          </g>
          <g
             id="g2560"
             style="mix-blend-mode:normal"
             figma:type="group">
            <g
               id="path20 fill"
               style="mix-blend-mode:normal"
               figma:type="vector">
              <path
                 id="use2557"
                 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"
                 style="mix-blend-mode:normal;fill:#616262" />
            </g>
          </g>
        </g>
      </g>
    </g>
  </g>
</svg>

```

--------------------------------------------------------------------------------
/jupyter_mcp_server/server.py:
--------------------------------------------------------------------------------

```python
# Copyright (c) 2023-2024 Datalayer, Inc.
#
# BSD 3-Clause License

import logging
import click
import httpx
import uvicorn
from typing import Union, Optional
from fastapi import Request
from jupyter_kernel_client import KernelClient
from jupyter_server_api import JupyterServerClient
from mcp.server import FastMCP
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.middleware.cors import CORSMiddleware

from jupyter_mcp_server.models import DocumentRuntime
from jupyter_mcp_server.utils import (
    extract_output, 
    safe_extract_outputs, 
    create_kernel,
    start_kernel,
    ensure_kernel_alive,
    execute_cell_with_timeout,
    execute_cell_with_forced_sync,
    is_kernel_busy,
    wait_for_kernel_idle,
    safe_notebook_operation,
    list_files_recursively,
)
from jupyter_mcp_server.config import get_config, set_config
from jupyter_mcp_server.notebook_manager import NotebookManager
from jupyter_mcp_server.enroll import auto_enroll_document
from jupyter_mcp_server.tools import (
    # Tool infrastructure
    ServerMode,
    # Notebook Management
    ListNotebooksTool,
    UseNotebookTool,
    RestartNotebookTool,
    UnuseNotebookTool,
    # Cell Reading
    ReadCellsTool,
    ListCellsTool,
    ReadCellTool,
    # Cell Writing
    InsertCellTool,
    InsertExecuteCodeCellTool,
    OverwriteCellSourceTool,
    DeleteCellTool,
    # Cell Execution
    ExecuteCellTool,
    # Other Tools
    AssignKernelToNotebookTool,
    ExecuteIpythonTool,
    ListFilesTool,
    ListKernelsTool,
)
from typing import Literal, Union
from mcp.types import ImageContent


###############################################################################


logger = logging.getLogger(__name__)


###############################################################################

class FastMCPWithCORS(FastMCP):
    def streamable_http_app(self) -> Starlette:
        """Return StreamableHTTP server app with CORS middleware
        See: https://github.com/modelcontextprotocol/python-sdk/issues/187
        """
        # Get the original Starlette app
        app = super().streamable_http_app()
        
        # Add CORS middleware
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],  # In production, should set specific domains
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )
        return app
    
    def sse_app(self, mount_path: str | None = None) -> Starlette:
        """Return SSE server app with CORS middleware"""
        # Get the original Starlette app
        app = super().sse_app(mount_path)
        # Add CORS middleware
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],  # In production, should set specific domains
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],
        )        
        return app


###############################################################################


mcp = FastMCPWithCORS(name="Jupyter MCP Server", json_response=False, stateless_http=True)

# Initialize the unified notebook manager
notebook_manager = NotebookManager()

# Initialize all tool instances (no arguments needed - tools receive dependencies via execute())
# Notebook Management Tools
list_notebook_tool = ListNotebooksTool()
use_notebook_tool = UseNotebookTool()
restart_notebook_tool = RestartNotebookTool()
unuse_notebook_tool = UnuseNotebookTool()

# Cell Reading Tools
read_cells_tool = ReadCellsTool()
list_cells_tool = ListCellsTool()
read_cell_tool = ReadCellTool()

# Cell Writing Tools
insert_cell_tool = InsertCellTool()
insert_execute_code_cell_tool = InsertExecuteCodeCellTool()
overwrite_cell_source_tool = OverwriteCellSourceTool()
delete_cell_tool = DeleteCellTool()

# Cell Execution Tools
execute_cell_tool = ExecuteCellTool()

# Other Tools
assign_kernel_to_notebook_tool = AssignKernelToNotebookTool()
execute_ipython_tool = ExecuteIpythonTool()
list_files_tool = ListFilesTool()
list_kernel_tool = ListKernelsTool()


###############################################################################


class ServerContext:
    """Singleton to cache server mode and context managers."""
    _instance = None
    _mode = None
    _contents_manager = None
    _kernel_manager = None
    _kernel_spec_manager = None
    _session_manager = None
    _server_client = None
    _kernel_client = None
    _initialized = False
    
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance
    
    @classmethod
    def reset(cls):
        """Reset the singleton instance. Use this when config changes."""
        if cls._instance is not None:
            cls._instance._initialized = False
            cls._instance._mode = None
            cls._instance._contents_manager = None
            cls._instance._kernel_manager = None
            cls._instance._kernel_spec_manager = None
            cls._instance._session_manager = None
            cls._instance._server_client = None
            cls._instance._kernel_client = None
    
    def initialize(self):
        """Initialize context once."""
        if self._initialized:
            return
        
        try:
            from jupyter_mcp_server.jupyter_extension.context import get_server_context
            context = get_server_context()
            
            if context.is_local_document() and context.get_contents_manager() is not None:
                self._mode = ServerMode.JUPYTER_SERVER
                self._contents_manager = context.get_contents_manager()
                self._kernel_manager = context.get_kernel_manager()
                self._kernel_spec_manager = context.get_kernel_spec_manager() if hasattr(context, 'get_kernel_spec_manager') else None
                self._session_manager = context.get_session_manager() if hasattr(context, 'get_session_manager') else None
            else:
                self._mode = ServerMode.MCP_SERVER
                # Initialize HTTP clients for MCP_SERVER mode
                config = get_config()
                
                # Validate that runtime_url is set and not None/empty
                # Note: String "None" values should have been normalized by start_command()
                runtime_url = config.runtime_url
                if not runtime_url or runtime_url in ("None", "none", "null", ""):
                    raise ValueError(
                        f"runtime_url is not configured (current value: {repr(runtime_url)}). "
                        "Please check:\n"
                        "1. RUNTIME_URL environment variable is set correctly (not the string 'None')\n"
                        "2. --runtime-url argument is provided when starting the server\n"
                        "3. The MCP client configuration passes runtime_url correctly"
                    )
                
                logger.info(f"Initializing MCP_SERVER mode with runtime_url: {runtime_url}")
                self._server_client = JupyterServerClient(base_url=runtime_url, token=config.runtime_token)
                # kernel_client will be created lazily when needed
        except (ImportError, Exception) as e:
            # If not in Jupyter context, use MCP_SERVER mode
            if not isinstance(e, ValueError):
                self._mode = ServerMode.MCP_SERVER
                # Initialize HTTP clients for MCP_SERVER mode
                config = get_config()
                
                # Validate that runtime_url is set and not None/empty
                # Note: String "None" values should have been normalized by start_command()
                runtime_url = config.runtime_url
                if not runtime_url or runtime_url in ("None", "none", "null", ""):
                    raise ValueError(
                        f"runtime_url is not configured (current value: {repr(runtime_url)}). "
                        "Please check:\n"
                        "1. RUNTIME_URL environment variable is set correctly (not the string 'None')\n"
                        "2. --runtime-url argument is provided when starting the server\n"
                        "3. The MCP client configuration passes runtime_url correctly"
                    )
                
                logger.info(f"Initializing MCP_SERVER mode with runtime_url: {runtime_url}")
                self._server_client = JupyterServerClient(base_url=runtime_url, token=config.runtime_token)
            else:
                raise
        
        self._initialized = True
        logger.info(f"Server mode initialized: {self._mode}")
    
    @property
    def mode(self):
        if not self._initialized:
            self.initialize()
        return self._mode
    
    @property
    def contents_manager(self):
        if not self._initialized:
            self.initialize()
        return self._contents_manager
    
    @property
    def kernel_manager(self):
        if not self._initialized:
            self.initialize()
        return self._kernel_manager
    
    @property
    def kernel_spec_manager(self):
        if not self._initialized:
            self.initialize()
        return self._kernel_spec_manager
    
    @property
    def session_manager(self):
        if not self._initialized:
            self.initialize()
        return self._session_manager
    
    @property
    def server_client(self):
        if not self._initialized:
            self.initialize()
        return self._server_client
    
    @property
    def kernel_client(self):
        if not self._initialized:
            self.initialize()
        return self._kernel_client


# Initialize server context singleton
server_context = ServerContext.get_instance()


###############################################################################


def __create_kernel() -> KernelClient:
    """Create a new kernel instance using current configuration."""
    config = get_config()
    return create_kernel(config, logger)


def __start_kernel():
    """Start the Jupyter kernel with error handling (for backward compatibility)."""
    config = get_config()
    start_kernel(notebook_manager, config, logger)


async def __auto_enroll_document():
    """Wrapper for auto_enroll_document that uses server context."""
    await auto_enroll_document(
        config=get_config(),
        notebook_manager=notebook_manager,
        use_notebook_tool=use_notebook_tool,
        server_context=server_context,
    )


def __ensure_kernel_alive() -> KernelClient:
    """Ensure kernel is running, restart if needed."""
    current_notebook = notebook_manager.get_current_notebook() or "default"
    return ensure_kernel_alive(notebook_manager, current_notebook, __create_kernel)


async def __execute_cell_with_timeout(notebook, cell_index, kernel, timeout_seconds=300):
    """Execute a cell with timeout and real-time output sync."""
    return await execute_cell_with_timeout(notebook, cell_index, kernel, timeout_seconds, logger)


async def __execute_cell_with_forced_sync(notebook, cell_index, kernel, timeout_seconds=300):
    """Execute cell with forced real-time synchronization."""
    return await execute_cell_with_forced_sync(notebook, cell_index, kernel, timeout_seconds, logger)


def __is_kernel_busy(kernel):
    """Check if kernel is currently executing something."""
    return is_kernel_busy(kernel)


async def __wait_for_kernel_idle(kernel, max_wait_seconds=60):
    """Wait for kernel to become idle before proceeding."""
    return await wait_for_kernel_idle(kernel, logger, max_wait_seconds)


async def __safe_notebook_operation(operation_func, max_retries=3):
    """Safely execute notebook operations with connection recovery."""
    return await safe_notebook_operation(operation_func, logger, max_retries)


def _list_files_recursively(server_client, current_path="", current_depth=0, files=None, max_depth=3):
    """Recursively list all files and directories in the Jupyter server."""
    return list_files_recursively(server_client, current_path, current_depth, files, max_depth)


###############################################################################
# Custom Routes.


@mcp.custom_route("/api/connect", ["PUT"])
async def connect(request: Request):
    """Connect to a document and a runtime from the Jupyter MCP server."""

    data = await request.json()
    
    # Log the received data for diagnostics
    # Note: set_config() will automatically normalize string "None" values
    logger.info(
        f"Connect endpoint received - runtime_url: {repr(data.get('runtime_url'))}, "
        f"document_url: {repr(data.get('document_url'))}, "
        f"provider: {data.get('provider')}"
    )

    document_runtime = DocumentRuntime(**data)

    # Clean up existing default notebook if any
    if "default" in notebook_manager:
        try:
            notebook_manager.remove_notebook("default")
        except Exception as e:
            logger.warning(f"Error stopping existing notebook during connect: {e}")

    # Update configuration with new values
    # String "None" values will be automatically normalized by set_config()
    set_config(
        provider=document_runtime.provider,
        runtime_url=document_runtime.runtime_url,
        runtime_id=document_runtime.runtime_id,
        runtime_token=document_runtime.runtime_token,
        document_url=document_runtime.document_url,
        document_id=document_runtime.document_id,
        document_token=document_runtime.document_token
    )
    
    # Reset ServerContext to pick up new configuration
    ServerContext.reset()

    try:
        __start_kernel()
        return JSONResponse({"success": True})
    except Exception as e:
        logger.error(f"Failed to connect: {e}")
        return JSONResponse({"success": False, "error": str(e)}, status_code=500)


@mcp.custom_route("/api/stop", ["DELETE"])
async def stop(request: Request):
    try:
        current_notebook = notebook_manager.get_current_notebook() or "default"
        if current_notebook in notebook_manager:
            notebook_manager.remove_notebook(current_notebook)
        return JSONResponse({"success": True})
    except Exception as e:
        logger.error(f"Error stopping notebook: {e}")
        return JSONResponse({"success": False, "error": str(e)}, status_code=500)


@mcp.custom_route("/api/healthz", ["GET"])
async def health_check(request: Request):
    """Custom health check endpoint"""
    kernel_status = "unknown"
    try:
        current_notebook = notebook_manager.get_current_notebook() or "default"
        kernel = notebook_manager.get_kernel(current_notebook)
        if kernel:
            kernel_status = "alive" if hasattr(kernel, 'is_alive') and kernel.is_alive() else "dead"
        else:
            kernel_status = "not_initialized"
    except Exception:
        kernel_status = "error"
    return JSONResponse(
        {
            "success": True,
            "service": "jupyter-mcp-server",
            "message": "Jupyter MCP Server is running.",
            "status": "healthy",
            "kernel_status": kernel_status,
        }
    )


###############################################################################
# Tools.
###############################################################################

###############################################################################
# Multi-Notebook Management Tools.


@mcp.tool()
async def use_notebook(
    notebook_name: str,
    notebook_path: Optional[str] = None,
    mode: Literal["connect", "create"] = "connect",
    kernel_id: Optional[str] = None,
) -> str:
    """Use a notebook file (connect to existing or create new, or switch to already-connected notebook).
    
    Args:
        notebook_name: Unique identifier for the notebook
        notebook_path: Path to the notebook file, relative to the Jupyter server root (e.g. "notebook.ipynb"). 
                      Optional - if not provided, switches to an already-connected notebook with the given name.
        mode: "connect" to connect to existing, "create" to create new
        kernel_id: Specific kernel ID to use (optional, will create new if not provided)
        
    Returns:
        str: Success message with notebook information
    """
    config = get_config()
    return await __safe_notebook_operation(
        lambda: use_notebook_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            notebook_name=notebook_name,
            notebook_path=notebook_path,
            use_mode=mode,
            kernel_id=kernel_id,
            ensure_kernel_alive_fn=__ensure_kernel_alive,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            session_manager=server_context.session_manager,
            notebook_manager=notebook_manager,
            runtime_url=config.runtime_url if config.runtime_url != "local" else None,
            runtime_token=config.runtime_token,
        )
    )


@mcp.tool()
async def list_notebooks() -> str:
    """List all notebooks in the Jupyter server (including subdirectories) and show which ones are managed.
    
    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.
    
    Returns:
        str: TSV formatted table with notebook information including management status
    """
    return await list_notebook_tool.execute(
        mode=server_context.mode,
        server_client=server_context.server_client,
        contents_manager=server_context.contents_manager,
        kernel_manager=server_context.kernel_manager,
        notebook_manager=notebook_manager,
    )


@mcp.tool()
async def restart_notebook(notebook_name: str) -> str:
    """Restart the kernel for a specific notebook.
    
    Args:
        notebook_name: Notebook identifier to restart
        
    Returns:
        str: Success message
    """
    return await restart_notebook_tool.execute(
        mode=server_context.mode,
        notebook_name=notebook_name,
        notebook_manager=notebook_manager,
        kernel_manager=server_context.kernel_manager,
    )


@mcp.tool()
async def unuse_notebook(notebook_name: str) -> str:
    """Unuse from a specific notebook and release its resources.
    
    Args:
        notebook_name: Notebook identifier to disconnect
        
    Returns:
        str: Success message
    """
    return await unuse_notebook_tool.execute(
        mode=server_context.mode,
        notebook_name=notebook_name,
        notebook_manager=notebook_manager,
        kernel_manager=server_context.kernel_manager,
    )


###############################################################################
# Cell Tools.

@mcp.tool()
async def insert_cell(
    cell_index: int,
    cell_type: Literal["code", "markdown"],
    cell_source: str,
) -> str:
    """Insert a cell to specified position.

    Args:
        cell_index: target index for insertion (0-based). Use -1 to append at end.
        cell_type: Type of cell to insert ("code" or "markdown")
        cell_source: Source content for the cell

    Returns:
        str: Success message and the structure of its surrounding cells (up to 5 cells above and 5 cells below)
    """
    return await __safe_notebook_operation(
        lambda: insert_cell_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
            cell_source=cell_source,
            cell_type=cell_type,
        )
    )


@mcp.tool()
async def insert_execute_code_cell(cell_index: int, cell_source: str) -> list[Union[str, ImageContent]]:
    """Insert and execute a code cell in a Jupyter notebook.

    Args:
        cell_index: Index of the cell to insert (0-based). Use -1 to append at end and execute.
        cell_source: Code source

    Returns:
        list[Union[str, ImageContent]]: List of outputs from the executed cell
    """
    return await __safe_notebook_operation(
        lambda: insert_execute_code_cell_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
            cell_source=cell_source,
            ensure_kernel_alive=__ensure_kernel_alive,
        )
    )


@mcp.tool()
async def overwrite_cell_source(cell_index: int, cell_source: str) -> str:
    """Overwrite the source of an existing cell.
       Note this does not execute the modified cell by itself.

    Args:
        cell_index: Index of the cell to overwrite (0-based)
        cell_source: New cell source - must match existing cell type

    Returns:
        str: Success message with diff showing changes made
    """
    return await __safe_notebook_operation(
        lambda: overwrite_cell_source_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
            cell_source=cell_source,
        )
    )

@mcp.tool()
async def execute_cell(cell_index: int, timeout_seconds: int = 300, stream: bool = False, progress_interval: int = 5) -> list[Union[str, ImageContent]]:
    """Execute a cell with configurable timeout and optional streaming progress updates.

    Args:
        cell_index: Index of the cell to execute (0-based)
        timeout_seconds: Maximum time to wait for execution (default: 300s)
        stream: Enable streaming progress updates for long-running cells (default: False)
        progress_interval: Seconds between progress updates when stream=True (default: 5s)
    Returns:
        list[Union[str, ImageContent]]: List of outputs from the executed cell
    """
    return await __safe_notebook_operation(
        lambda: execute_cell_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
            timeout_seconds=timeout_seconds,
            stream=stream,
            progress_interval=progress_interval,
            ensure_kernel_alive_fn=__ensure_kernel_alive,
            wait_for_kernel_idle_fn=__wait_for_kernel_idle,
            safe_extract_outputs_fn=safe_extract_outputs,
            execute_cell_with_forced_sync_fn=__execute_cell_with_forced_sync,
            extract_output_fn=extract_output,
        ),
        max_retries=1
    )


@mcp.tool()
async def read_cells() -> list[dict[str, Union[str, int, list[Union[str, ImageContent]]]]]:
    """Read all cells from the Jupyter notebook.
    Returns:
        list[dict]: List of cell information including index, type, source,
                    and outputs (for code cells)
    """
    return await __safe_notebook_operation(
        lambda: read_cells_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            notebook_manager=notebook_manager,
        )
    )


@mcp.tool()
async def list_cells() -> str:
    """List the basic information of all cells in the notebook.
    
    Returns a formatted table showing the index, type, execution count (for code cells),
    and first line of each cell. This provides a quick overview of the notebook structure
    and is useful for locating specific cells for operations like delete or insert.
    
    Returns:
        str: Formatted table with cell information (Index, Type, Count, First Line)
    """
    return await __safe_notebook_operation(
        lambda: list_cells_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            notebook_manager=notebook_manager,
        )
    )


@mcp.tool()
async def read_cell(cell_index: int) -> dict[str, Union[str, int, list[Union[str, ImageContent]]]]:
    """Read a specific cell from the Jupyter notebook.
    Args:
        cell_index: Index of the cell to read (0-based)
    Returns:
        dict: Cell information including index, type, source, and outputs (for code cells)
    """
    return await __safe_notebook_operation(
        lambda: read_cell_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
        )
    )

@mcp.tool()
async def delete_cell(cell_index: int) -> str:
    """Delete a specific cell from the Jupyter notebook.
    Args:
        cell_index: Index of the cell to delete (0-based)
    Returns:
        str: Success message
    """
    return await __safe_notebook_operation(
        lambda: delete_cell_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            cell_index=cell_index,
        )
    )


@mcp.tool()
async def execute_ipython(code: str, timeout: int = 60) -> list[Union[str, ImageContent]]:
    """Execute IPython code directly in the kernel on the current active notebook.
    
    This powerful tool supports:
    1. Magic commands (e.g., %timeit, %who, %load, %run, %matplotlib)
    2. Shell commands (e.g., !pip install, !ls, !cat)
    3. Python code (e.g., print(df.head()), df.info())
    
    Use cases:
    - Performance profiling and debugging
    - Environment exploration and package management
    - Variable inspection and data analysis
    - File system operations on Jupyter server
    - Temporary calculations and quick tests

    Args:
        code: IPython code to execute (supports magic commands, shell commands with !, and Python code)
        timeout: Execution timeout in seconds (default: 60s)
    Returns:
        List of outputs from the executed code
    """
    # Get kernel_id for JUPYTER_SERVER mode
    # Let the tool handle getting kernel_id via get_current_notebook_context()
    kernel_id = None
    if server_context.mode == ServerMode.JUPYTER_SERVER:
        current_notebook = notebook_manager.get_current_notebook() or "default"
        kernel_id = notebook_manager.get_kernel_id(current_notebook)
        # Note: kernel_id might be None here if notebook not in manager,
        # but the tool will fall back to config values via get_current_notebook_context()
    
    return await __safe_notebook_operation(
        lambda: execute_ipython_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            kernel_manager=server_context.kernel_manager,
            notebook_manager=notebook_manager,
            code=code,
            timeout=timeout,
            kernel_id=kernel_id,
            ensure_kernel_alive_fn=__ensure_kernel_alive,
            wait_for_kernel_idle_fn=__wait_for_kernel_idle,
            safe_extract_outputs_fn=safe_extract_outputs,
        ),
        max_retries=1
    )


@mcp.tool()
async def list_files(path: str = "", max_depth: int = 3) -> str:
    """List all files and directories in the Jupyter server's file system.
    
    This tool recursively lists files and directories from the Jupyter server's content API,
    showing the complete file structure including notebooks, data files, scripts, and directories.
    
    Args:
        path: The starting path to list from (empty string means root directory)
        max_depth: Maximum depth to recurse into subdirectories (default: 3)
        
    Returns:
        str: Tab-separated table with columns: Path, Type, Size, Last_Modified
    """
    return await __safe_notebook_operation(
        lambda: list_files_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            path=path,
            max_depth=max_depth,
            list_files_recursively_fn=_list_files_recursively,
        )
    )


@mcp.tool()
async def list_kernels() -> str:
    """List all available kernels in the Jupyter server.
    
    This tool shows all running and available kernel sessions on the Jupyter server,
    including their IDs, names, states, connection information, and kernel specifications.
    Useful for monitoring kernel resources and identifying specific kernels for connection.
    
    Returns:
        str: Tab-separated table with columns: ID, Name, Display_Name, Language, State, Connections, Last_Activity, Environment
    """
    return await __safe_notebook_operation(
        lambda: list_kernel_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            kernel_manager=server_context.kernel_manager,
            kernel_spec_manager=server_context.kernel_spec_manager,
        )
    )


@mcp.tool()
async def assign_kernel_to_notebook(
    notebook_path: str,
    kernel_id: str,
    session_name: str = None
) -> str:
    """Assign a kernel to a notebook by creating a Jupyter session.
    
    This creates a Jupyter server session that connects a notebook file to a kernel,
    enabling code execution in the notebook. Sessions are the mechanism Jupyter uses
    to maintain the relationship between notebooks and their kernels.
    
    Args:
        notebook_path: Path to the notebook file, relative to the Jupyter server root (e.g. "notebook.ipynb")
        kernel_id: ID of the kernel to assign to the notebook
        session_name: Optional name for the session (defaults to notebook path)
    
    Returns:
        str: Success message with session information including session ID
    """
    return await __safe_notebook_operation(
        lambda: assign_kernel_to_notebook_tool.execute(
            mode=server_context.mode,
            server_client=server_context.server_client,
            contents_manager=server_context.contents_manager,
            session_manager=server_context.session_manager,
            kernel_manager=server_context.kernel_manager,
            notebook_path=notebook_path,
            kernel_id=kernel_id,
            session_name=session_name,
        )
    )


###############################################################################
# Helper Functions for Extension.


async def get_registered_tools():
    """
    Get list of all registered MCP tools with their metadata.
    
    This function is used by the Jupyter extension to dynamically expose
    the tool registry without hardcoding tool names and parameters.
    
    Returns:
        list: List of tool dictionaries with name, description, and inputSchema
    """
    # Use FastMCP's list_tools method which returns Tool objects
    tools_list = await mcp.list_tools()
    
    tools = []
    for tool in tools_list:
        tool_dict = {
            "name": tool.name,
            "description": tool.description,
        }
        
        # Extract parameter names from inputSchema
        if hasattr(tool, 'inputSchema') and tool.inputSchema:
            input_schema = tool.inputSchema
            if 'properties' in input_schema:
                tool_dict["parameters"] = list(input_schema['properties'].keys())
            else:
                tool_dict["parameters"] = []
            
            # Include full inputSchema for MCP protocol compatibility
            tool_dict["inputSchema"] = input_schema
        else:
            tool_dict["parameters"] = []
        
        tools.append(tool_dict)
    
    return tools


###############################################################################
# Commands.


# Shared options decorator to reduce code duplication
def _common_options(f):
    """Decorator that adds common start options to a command."""
    options = [
        click.option(
            "--provider",
            envvar="PROVIDER",
            type=click.Choice(["jupyter", "datalayer"]),
            default="jupyter",
            help="The provider to use for the document and runtime. Defaults to 'jupyter'.",
        ),
        click.option(
            "--runtime-url",
            envvar="RUNTIME_URL",
            type=click.STRING,
            default="http://localhost:8888",
            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.",
        ),
        click.option(
            "--runtime-id",
            envvar="RUNTIME_ID",
            type=click.STRING,
            default=None,
            help="The kernel ID to use. If not provided, a new kernel should be started.",
        ),
        click.option(
            "--runtime-token",
            envvar="RUNTIME_TOKEN",
            type=click.STRING,
            default=None,
            help="The runtime token to use for authentication with the provider. If not provided, the provider should accept anonymous requests.",
        ),
        click.option(
            "--document-url",
            envvar="DOCUMENT_URL",
            type=click.STRING,
            default="http://localhost:8888",
            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.",
        ),
        click.option(
            "--document-id",
            envvar="DOCUMENT_ID",
            type=click.STRING,
            default=None,
            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.",
        ),
        click.option(
            "--document-token",
            envvar="DOCUMENT_TOKEN",
            type=click.STRING,
            default=None,
            help="The document token to use for authentication with the provider. If not provided, the provider should accept anonymous requests.",
        )
    ]
    # Apply decorators in reverse order
    for option in reversed(options):
        f = option(f)
    return f

def _do_start(
    transport: str,
    start_new_runtime: bool,
    runtime_url: str,
    runtime_id: str,
    runtime_token: str,
    document_url: str,
    document_id: str,
    document_token: str,
    port: int,
    provider: str,
):
    """Internal function to execute the start logic."""
    
    # Log the received configuration for diagnostics
    # Note: set_config() will automatically normalize string "None" values
    logger.info(
        f"Start command received - runtime_url: {repr(runtime_url)}, "
        f"document_url: {repr(document_url)}, provider: {provider}, "
        f"transport: {transport}"
    )

    # Set configuration using the singleton
    # String "None" values will be automatically normalized by set_config()
    config = set_config(
        transport=transport,
        provider=provider,
        runtime_url=runtime_url,
        start_new_runtime=start_new_runtime,
        runtime_id=runtime_id,
        runtime_token=runtime_token,
        document_url=document_url,
        document_id=document_id,
        document_token=document_token,
        port=port
    )
    
    # Reset ServerContext to pick up new configuration
    ServerContext.reset()

    # Determine startup behavior based on configuration
    if config.document_id:
        # If document_id is provided, auto-enroll the notebook
        # Kernel creation depends on start_new_runtime and runtime_id flags
        try:
            import asyncio
            # Run the async enrollment in the event loop
            asyncio.run(__auto_enroll_document())
        except Exception as e:
            logger.error(f"Failed to auto-enroll document '{config.document_id}': {e}")
            # Fallback to legacy kernel-only mode if enrollment fails
            if config.start_new_runtime or config.runtime_id:
                try:
                    __start_kernel()
                except Exception as e2:
                    logger.error(f"Failed to start kernel on startup: {e2}")
    elif config.start_new_runtime or config.runtime_id:
        # If no document_id but start_new_runtime/runtime_id is set, just create kernel
        # This is for backward compatibility - kernel without managed notebook
        try:
            __start_kernel()
        except Exception as e:
            logger.error(f"Failed to start kernel on startup: {e}")
    # else: No startup action - user must manually enroll notebooks or create kernels

    logger.info(f"Starting Jupyter MCP Server with transport: {transport}")

    if transport == "stdio":
        mcp.run(transport="stdio")
    elif transport == "streamable-http":
        uvicorn.run(mcp.streamable_http_app, host="0.0.0.0", port=port)  # noqa: S104
    else:
        raise Exception("Transport should be `stdio` or `streamable-http`.")


@click.group(invoke_without_command=True)
@_common_options
@click.option(
    "--transport",
    envvar="TRANSPORT",
    type=click.Choice(["stdio", "streamable-http"]),
    default="stdio",
    help="The transport to use for the MCP server. Defaults to 'stdio'.",
)
@click.option(
    "--start-new-runtime",
    envvar="START_NEW_RUNTIME",
    type=click.BOOL,
    default=True,
    help="Start a new runtime or use an existing one.",
)
@click.option(
    "--port",
    envvar="PORT",
    type=click.INT,
    default=4040,
    help="The port to use for the Streamable HTTP transport. Ignored for stdio transport.",
)
@click.pass_context
def server(
    ctx,
    transport: str,
    start_new_runtime: bool,
    runtime_url: str,
    runtime_id: str,
    runtime_token: str,
    document_url: str,
    document_id: str,
    document_token: str,
    port: int,
    provider: str,
):
    """Manages Jupyter MCP Server.
    
    When invoked without subcommands, starts the MCP server directly.
    This allows for quick startup with: uvx jupyter-mcp-server
    
    Subcommands (start, connect, stop) are still available for advanced use cases.
    """
    # If a subcommand is invoked, let it handle the execution
    if ctx.invoked_subcommand is not None:
        return
    
    # No subcommand provided - execute the default start behavior
    _do_start(
        transport=transport,
        start_new_runtime=start_new_runtime,
        runtime_url=runtime_url,
        runtime_id=runtime_id,
        runtime_token=runtime_token,
        document_url=document_url,
        document_id=document_id,
        document_token=document_token,
        port=port,
        provider=provider,
    )


@server.command("connect")
@_common_options
@click.option(
    "--jupyter-mcp-server-url",
    envvar="JUPYTER_MCP_SERVER_URL",
    type=click.STRING,
    default="http://localhost:4040",
    help="The URL of the Jupyter MCP Server to connect to. Defaults to 'http://localhost:4040'.",
)
def connect_command(
    jupyter_mcp_server_url: str,
    runtime_url: str,
    runtime_id: str,
    runtime_token: str,
    document_url: str,
    document_id: str,
    document_token: str,
    provider: str,
):
    """Command to connect a Jupyter MCP Server to a document and a runtime."""

    # Set configuration using the singleton
    set_config(
        provider=provider,
        runtime_url=runtime_url,
        runtime_id=runtime_id,
        runtime_token=runtime_token,
        document_url=document_url,
        document_id=document_id,
        document_token=document_token
    )

    config = get_config()
    
    document_runtime = DocumentRuntime(
        provider=config.provider,
        runtime_url=config.runtime_url,
        runtime_id=config.runtime_id,
        runtime_token=config.runtime_token,
        document_url=config.document_url,
        document_id=config.document_id,
        document_token=config.document_token,
    )

    r = httpx.put(
        f"{jupyter_mcp_server_url}/api/connect",
        headers={
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        content=document_runtime.model_dump_json(),
    )
    r.raise_for_status()


@server.command("stop")
@click.option(
    "--jupyter-mcp-server-url",
    envvar="JUPYTER_MCP_SERVER_URL",
    type=click.STRING,
    default="http://localhost:4040",
    help="The URL of the Jupyter MCP Server to stop. Defaults to 'http://localhost:4040'.",
)
def stop_command(jupyter_mcp_server_url: str):
    r = httpx.delete(
        f"{jupyter_mcp_server_url}/api/stop",
    )
    r.raise_for_status()


@server.command("start")
@_common_options
@click.option(
    "--transport",
    envvar="TRANSPORT",
    type=click.Choice(["stdio", "streamable-http"]),
    default="stdio",
    help="The transport to use for the MCP server. Defaults to 'stdio'.",
)
@click.option(
    "--start-new-runtime",
    envvar="START_NEW_RUNTIME",
    type=click.BOOL,
    default=True,
    help="Start a new runtime or use an existing one.",
)
@click.option(
    "--port",
    envvar="PORT",
    type=click.INT,
    default=4040,
    help="The port to use for the Streamable HTTP transport. Ignored for stdio transport.",
)
def start_command(
    transport: str,
    start_new_runtime: bool,
    runtime_url: str,
    runtime_id: str,
    runtime_token: str,
    document_url: str,
    document_id: str,
    document_token: str,
    port: int,
    provider: str,
):
    """Start the Jupyter MCP server with a transport."""
    _do_start(
        transport=transport,
        start_new_runtime=start_new_runtime,
        runtime_url=runtime_url,
        runtime_id=runtime_id,
        runtime_token=runtime_token,
        document_url=document_url,
        document_id=document_id,
        document_token=document_token,
        port=port,
        provider=provider,
    )


###############################################################################
# Main.


if __name__ == "__main__":
    start_command()
```
Page 4/5FirstPrevNextLast