From c18c71c6fc286da0d4e8bcfb44dc334ed6e0d81d Mon Sep 17 00:00:00 2001 From: erjemin Date: Thu, 15 Oct 2026 11:33:16 +0300 Subject: [PATCH] =?UTF-8?q?add:=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=83=D0=BD=D0=B8=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20MCP-=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20LLM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализован MCP-сервер на стандартной библиотеке Python 3.12+ (main.py) - Добавлены инструменты ask_local_model, check_local_model_status, review_code_locally - Поддержка oMLX, LM Studio, Ollama, llama.cpp, vLLM и других OpenAI-совместимых бэкендов - Добавлена документация (README.md), пример конфигурации (.env.example) и файлы проекта Poetry Co-authored-by: Junie --- .env.example | 7 + .gitignore | 716 ++++++++++++++++++++++++++++++ .junie/AGENTS.md | 16 + .junie/git-commit-instructions.md | 2 + README.md | 215 +++++++++ main.py | 438 ++++++++++++++++++ poetry.lock | 7 + pyproject.toml | 14 + 8 files changed, 1415 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .junie/AGENTS.md create mode 100644 .junie/git-commit-instructions.md create mode 100644 README.md create mode 100644 main.py create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f18efa0 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# ============================================== +# Интеграция с локальной нейросетью oMLX (MCP сервер) +# ============================================== +OMLX_BASE_URL=http://127.0.0.1:8008/v1 +OMLX_API_KEY=your_omlx_api_key_here +OMLX_MODEL_NAME=default +OMLX_TIMEOUT=120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0a11d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,716 @@ +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### Xcode template +## User settings +xcuserdata/ + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/ +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### VisualStudio template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### Windows template +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/.junie/AGENTS.md b/.junie/AGENTS.md new file mode 100644 index 0000000..c52979b --- /dev/null +++ b/.junie/AGENTS.md @@ -0,0 +1,16 @@ +* Ты — опытный помощник по программированию. +* Всегда отвечай на русском языке, если не указано иное. +* Подробно комментируй код, который ты создаешь. Объясняй функциональность в комментариях на русском языке. +* При написании кода следуй стандартам PEP 8 для Python. +* Используй современные возможности Python (3.12+). +* Читай в интернете документацию к используемым библиотекам прежде, чем писать код. +* Предпочитай `poetry` для управления зависимостями. +* Подробно комментируй каждое действие. Аннотируй функции, классы и их переменные с помощью docstring. +* Не делай git commit, git push и другие команды git, если специально не попросили. Если это нужно, предложи 2-3 варианта коммитов и сообщений к ним на русском языке. +* Избегай эмодзи в коде, комментариях, логах и документации. +* Если сказано "подумай", "без кода", "обсудим" — не надо писать код. Нужно поразмыслить, предложить варианты, объяснить, обсудить. +* Избегай написания отчетов об анализе, рекомендациях, "что сделано" и т.п пока не попросят. Если отчеты и другие документы создаются, размещай их в папке `_prj_blueprint/` в корне проекта. Внутри указывай дату, время. + +* Для локального ревью кода, генерации черновых вариантов или второго мнения используй MCP-инструменты oMLX (`ask_local_model`, `review_code_locally`). +* Архитектура взаимодействия: внешняя модель выступает главным координатором (валидирует требования, структуру проекта и стандарты PEP 8), а локальная oMLX — экспертом по проверке фрагментов кода и генерации идей. +* При недоступности или таймауте локальной модели продолжай работу самостоятельно, не блокируя задачу. \ No newline at end of file diff --git a/.junie/git-commit-instructions.md b/.junie/git-commit-instructions.md new file mode 100644 index 0000000..344fdae --- /dev/null +++ b/.junie/git-commit-instructions.md @@ -0,0 +1,2 @@ +* Не делай git commit, git push и другие команды git, если специально не попросили. Если это нужно, предложи 2-3 варианта коммитов и сообщений к ним на русском языке. +* Префиксы коммитов: `fix:` — исправление ошибки; `add:` — новый файл, модуль, метод, функциональность; `mod:` — изменения, улучшения, развите существующих методов, классов и т.п.; `ref:` — рефакторинг; `doc:` — документация; `tst:` — тесты; `minor:` — незначительные изменения, стиль кода, комментарии, форматирование; `chore:` — вспомогательные задачи, не влияющие на функциональность (например, обновление зависимостей). \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca17788 --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +# Универсальный MCP-сервер для локальных LLM (oMLX, LM Studio, Ollama, llama.cpp и др.) + +![Version](https://img.shields.io/badge/version-0.1.0-blue) +![License](https://img.shields.io/badge/license-MIT-green) +![Python](https://img.shields.io/badge/python-3.12-yellow) +[![Static Badge](https://img.shields.io/badge/ОРИГИНАЛ-git.cube2.ru-green)](https://git.cube2.ru/erjemin/2026-omlx-mcp-server) + +Сервер реализует протокол Model Context Protocol (MCP) через стандартные потоки ввода-вывода (`stdio`) и предоставляет AI-ассистентам (включая JetBrains Junie в PyCharm, Claude Desktop, Cursor и др.) доступ к локальным языковым моделям через любой OpenAI-совместимый REST API. + +Сервер изначально оптимизирован для **oMLX** (Apple Silicon MLX), но полностью совместим со всеми популярными локальными бэкендами: +- **[oMLX](https://omlx.ai/)** +- **[LM Studio](https://lmstudio.ai/)** +- **[Ollama](https://ollama.com/)** +- **[llama.cpp / llama-server](https://github.com/ggerganov/llama.cpp)** +- **[vLLM](https://docs.vllm.ai/)** +- **[LocalAI](https://localai.io/)** / **[Jan](https://jan.ai/)** / **[Text Generation WebUI (oobabooga)](https://github.com/oobabooga/text-generation-webui)** и так далее. + +Мой персональный выбор в пользу oMLX связан с тем, что в нем агент написания кода не аннулирует кэш ключ-значение (KV-cache) десятки раз за сессию, а сохраняет каждый блок кэша на SSD. Из-за этого агент, возвращаясь к предыдущему префиксу, восстанавливает контекст за миллисекунды, а не пересчитывает его с нуля. + +Реализация выполнена на стандартной библиотеке Python 3.12+ без сторонних зависимостей во время выполнения и оптимизирована для управления окружением через `poetry`. + +--- + +## Что такое MCP и зачем он нужен Junie + +**Model Context Protocol (MCP)** — это открытый стандартизированный протокол взаимодействия AI-ассистентов с внешними источниками контекста и исполнительными инструментами (tools). Вместо написания уникальных плагинов под каждую модель и IDE, MCP предоставляет единый протокол обмена сообщениями на базе JSON-RPC. + +### Почему интеграция через MCP полезна для Junie: + +1. **Двухуровневая архитектура (Гибридный интеллект):** + - **Junie (внешняя облачная модель)** выступает в роли главного архитектора и координатора: глубоко понимает проект, планирует шаги и ведет разработку. + - **Локальная модель (oMLX / LM Studio / Ollama)** выступает в роли быстрого эксперта: Junie может на лету делегировать локальной модели ревью фрагментов кода, проверку гипотез, брейншторм или черновые генерации через вызовы инструментов MCP (`ask_local_model`, `review_code_locally`). + +2. **Приватность и безопасность данных:** + - Чувствительные части кодовой базы, конфиденциальные данные или проприетарные алгоритмы можно анализировать локально, не передавая их во внешние облачные API. + +3. **Экономия токенов и лимитов:** + - Перенос вспомогательных вычислений (генерация документации, ревью, форматирование, альтернативные варианты) на локальную машину снижает расход контекстного окна и квот облачных провайдеров. + +4. **Полная независимость и автономность:** + - Вы можете менять локальный ИИ-движок или в фоновом режиме через `.env` или меняя default-модель (Qwen, DeepSeek, Llama, Mistral) в настройках локального ИИ-движка, не меняя настройки самого агента. + +--- + +## Возможности сервера + +Сервер предоставляет следующие инструменты (MCP Tools): + +1. `ask_local_model` — отправка запроса к локальной языковой модели для генерации текста, анализа, генерации альтернативных вариантов реализации или выполнения вспомогательных вычислений с сохранением приватности данных. + - Параметры: + - `prompt` (строка, обязательный) — основной текст запроса. + - `system_prompt` (строка, опциональный) — системная инструкция. + - `model` (строка, опциональный) — имя конкретной модели oMLX (по умолчанию берется активная/первая доступная). + - `temperature` (число, опциональный, по умолчанию 0.2) — температура генерации от 0.0 до 1.0. + - `max_tokens` (целое число, опциональный) — лимит на количество токенов в ответе. + +2. `check_local_model_status` — проверка сетевой доступности локального сервера oMLX и получение списка загруженных моделей. + - Параметры: отсутствуют. + +3. `review_code_locally` — проведение локального код-ревью фрагмента кода силами модели oMLX (поиск потенциальных дефектов, узких мест и предложений по рефакторингу). + - Параметры: + - `code_snippet` (строка, обязательный) — исходный код для анализа. + - `instructions` (строка, опциональный) — специальные указания для ревью. + - `model` (строка, опциональный) — имя модели oMLX. + +--- + +## Требования + +- Python версии 3.12 или выше. +- Менеджер пакетов `poetry`. +- Любой запущенный локальный сервер моделей с OpenAI-совместимым API (oMLX, LM Studio, Ollama, llama.cpp / llama-server, vLLM, Jan, LocalAI и др.). +- macOS, Linux или Windows. + +--- + +## Установка и настройка + +### 1. Клонирование и установка зависимостей + +Перейдите в директорию проекта и установите виртуальное окружение: + +```bash +cd /путь/к/2026-omlx-mcp-server +poetry install +``` + +### 2. Настройка переменных окружения + +Создайте файл `.env` на основе примера `.env.example`: + +```bash +cp .env.example .env +``` + +Отредактируйте `.env` под разные бэкенды: + + +```ini +OMLX_BASE_URL=http://127.0.0.1:8008/v1 +OMLX_API_KEY=укажите_ваш_API_ключ_если_нужен +OMLX_MODEL_NAME=default +OMLX_TIMEOUT=120 +``` + +Сервер автоматически считывает переменные из файла `.env`, расположенного рядом со скриптом `main.py` или в текущей рабочей директории, если они не были переданы напрямую через системное окружение. + +--- + +## Подключение к JetBrains Junie (PyCharm) + +Для подключения MCP-сервера к агенту Junie в PyCharm укажите конфигурацию в файле `.junie/mcp.json` в корне любого вашего рабочего проекта. + +Поскольку сервер реализован на **стандартной библиотеке Python без сторонних зависимостей**, его можно запускать напрямую через системный `python3` без накладных расходов на Poetry или виртуальные окружения. + +### Вариант 1. Прямой запуск через python3 (Рекомендуемый и самый надежный) + +Укажите прямой путь к скрипту `main.py` и параметры подключения в блоке `env`: + +```json +{ + "mcpServers": { + "omlx-local": { + "command": "python3", + "args": [ + "путь/к/omlx-mcp-server/main.py" + ], + "env": { + "OMLX_BASE_URL": "http://127.0.0.1:8008/v1", + "OMLX_MODEL_NAME": "default", + "OMLX_API_KEY": "укажите_ваш_API_ключ_если_нужен", + "OMLX_TIMEOUT": "120" + } + } + } +} +``` + +> **Преимущество:** Работает мгновенно из любого проекта, не зависит от наличия Poetry в `PATH` фоновых процессов IDE и позволяет задавать настройки прямо в конфигурации MCP. +> **Примечание:** Если `python3` не обнаружен в `PATH` внутри IDE или нужная версия Python находится в виртуальном окружении, укажите полный путь к исполняемому файлу (например, `/путь/к/omlx-mcp-server/.venv/bin/python` или `/usr/local/bin/python3`). + +### Вариант 2. Запуск через Poetry + +Если вы предпочитаете запускать сервер в изолированном окружении через Poetry: + +```json +{ + "mcpServers": { + "omlx-local": { + "command": "poetry", + "args": [ + "run", + "python", + "main.py" + ], + "cwd": "/Users/e-serg/PRJ/2026-omlx-mcp-server" + } + } +} +``` + +> **Примечание:** Если `poetry` не обнаружен в `PATH` внутри IDE, укажите полный путь к исполняемому файлу (например, `/Users/e-serg/.local/bin/poetry` или `~/.local/bin/poetry`). + +--- + +## Проверка работоспособности + +Сервер общается по протоколу JSON-RPC через стандартные потоки ввода-вывода (`stdin`/`stdout`). При прямом запуске в терминале он ожидает входные данные. + +### Способ 1. Быстрая проверка одной командой (через конвейер / pipe) + +Выполните команду в терминале: + +```bash +echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}}' | python3 main.py +``` + +Сервер мгновенно вернет JSON-ответ инициализации: +```json +{"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "omlx-local-bridge", "version": "1.0.0"}}} +``` + +Для проверки получения списка инструментов: + +```bash +echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}' | python3 main.py +``` + +### Способ 2. Интерактивный запуск + +1. Запустите скрипт: + ```bash + python3 main.py + ``` + *(или `poetry run python main.py`)* + +2. Скрипт перейдет в режим ожидания (курсор на пустой строке). Вставьте JSON-запрос инициализации и нажмите `Enter`: + ```json + {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}} + ``` + Получите ответ: + ```json + {"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "omlx-local-bridge", "version": "1.0.0"}}} + ``` + +3. Проверка работы локального ИИ: + ```json + {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "ask_local_model", "arguments": {"prompt": "Напиши японское хокку про язык программирования Python"}}} + ``` + Ответ сервера: + ```json + {"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text", "text": "Синтаксис прост — \nPython, как тихий поток, \nЛегко читать, легко писать."}], "isError": false}} + ``` + +4. Для выхода нажмите `Ctrl+C` или `Ctrl+D`. diff --git a/main.py b/main.py new file mode 100644 index 0000000..8a85ab9 --- /dev/null +++ b/main.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +""" +MCP (Model Context Protocol) сервер для интеграции локальных нейросетей в PyCharm / Junie. + +Поддерживает любые локальные LLM-серверы с OpenAI-совместимым REST API: +oMLX, LM Studio, Ollama, llama.cpp (llama-server), vLLM, Jan, LocalAI и др. + +Данный скрипт реализует стандартный протокол MCP по транспорту stdio с использованием +исключительно стандартной библиотеки Python 3.12+ (urllib, json, sys, os). +Он транслирует команды от внешнего агента (Junie) в запросы к локальному REST API. +""" + +from __future__ import annotations + +import json +import os +import sys +import urllib.error +import urllib.request +from typing import Any, Dict, List, Optional + + +def load_env_file() -> None: + """ + Загружает переменные окружения из файла .env в корне проекта, + если они еще не установлены в системном окружении. + Не перезаписывает уже существующие переменные окружения. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(script_dir) + candidate_paths = [ + os.path.join(script_dir, ".env"), + os.path.join(project_root, ".env"), + os.path.join(os.getcwd(), ".env"), + ] + + for env_path in candidate_paths: + if os.path.isfile(env_path): + try: + with open(env_path, "r", encoding="utf-8") as f: + for line in f: + clean_line = line.strip() + if not clean_line or clean_line.startswith("#") or "=" not in clean_line: + continue + key, val = clean_line.split("=", 1) + key = key.strip() + val = val.strip().strip('"').strip("'") + if key and key not in os.environ: + os.environ[key] = val + break + except Exception: + pass + + +# Автоматическая загрузка конфигурации из .env при старте скрипта +load_env_file() + +# Конфигурация по умолчанию (динамически берется из переменных окружения / .env) +DEFAULT_OMLX_BASE_URL: str = os.environ.get("OMLX_BASE_URL", "http://127.0.0.1:8008/v1") +DEFAULT_OMLX_API_KEY: str = os.environ.get("OMLX_API_KEY", "") +DEFAULT_OMLX_MODEL: str = os.environ.get("OMLX_MODEL_NAME", "default") +REQUEST_TIMEOUT_SECONDS: int = int(os.environ.get("OMLX_TIMEOUT", "120")) + + +def make_omlx_request(endpoint: str, data: Optional[Dict[str, Any]] = None, method: str = "GET") -> Dict[str, Any]: + """ + Выполняет HTTP-запрос к локальному REST API oMLX. + + :param endpoint: Относительный путь эндпоинта (например, '/chat/completions' или '/models'). + :param data: Тело запроса в формате словаря (для POST-запросов). + :param method: HTTP-метод ('GET' или 'POST'). + :return: Распарсенный JSON-ответ от сервера oMLX. + :raises RuntimeError: При ошибках подключения или получении некорректного ответа. + """ + base_url = DEFAULT_OMLX_BASE_URL.rstrip("/") + url = f"{base_url}{endpoint}" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {DEFAULT_OMLX_API_KEY}", + "User-Agent": "MCP-oMLX-Bridge/1.0", + } + + payload_bytes = json.dumps(data).encode("utf-8") if data is not None else None + request = urllib.request.Request(url=url, data=payload_bytes, headers=headers, method=method) + + try: + with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response: + raw_body = response.read().decode("utf-8") + return json.loads(raw_body) + except urllib.error.HTTPError as error: + err_msg = error.read().decode("utf-8", errors="replace") + raise RuntimeError(f"Ошибка oMLX API HTTP {error.code}: {err_msg}") from error + except urllib.error.URLError as error: + raise RuntimeError( + f"Не удалось подключиться к oMLX по адресу {url}. Проверьте, запущен ли oMLX. Ошибка: {error.reason}" + ) from error + except Exception as error: + raise RuntimeError(f"Непредвиденная ошибка при запросе к oMLX: {str(error)}") from error + + +def get_available_models() -> List[str]: + """ + Получает список идентификаторов моделей, доступных на сервере oMLX. + + :return: Список строк с названиями моделей. + """ + try: + result = make_omlx_request("/models", method="GET") + data = result.get("data", []) + return [m.get("id", "") for m in data if isinstance(m, dict) and m.get("id")] + except Exception: + return [] + + +def resolve_model_name(requested_model: str = "") -> str: + """ + Определяет точное имя модели для использования в запросе. + Если имя не указано или равно 'default', пытается автоматически выбрать + первую доступную модель из списка активных на локальном сервере oMLX. + + :param requested_model: Запрошенное пользователем или конфигурацией имя модели. + :return: Выбранное имя модели. + """ + clean_model = requested_model.strip() + if clean_model and clean_model.lower() != "default": + return clean_model + + if DEFAULT_OMLX_MODEL and DEFAULT_OMLX_MODEL.lower() != "default": + return DEFAULT_OMLX_MODEL + + available_models = get_available_models() + if available_models: + return available_models[0] + + return "default" + + +def tool_ask_local_model( + prompt: str, + system_prompt: str = "", + model: str = "", + temperature: float = 0.2, + max_tokens: Optional[int] = None, +) -> str: + """ + Отправляет запрос к локальной модели oMLX для генерации текста или решения задачи. + + :param prompt: Текст запроса (пользовательский промпт). + :param system_prompt: Опциональная системная инструкция. + :param model: Имя модели (если пусто, выбирается активная модель oMLX). + :param temperature: Температура генерации (от 0.0 до 1.0). + :param max_tokens: Ограничение на максимальное количество токенов в ответе. + :return: Сгенерированный локальной моделью текст ответа. + """ + target_model = resolve_model_name(model) + + messages: List[Dict[str, str]] = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + messages.append({"role": "user", "content": prompt}) + + payload: Dict[str, Any] = { + "model": target_model, + "messages": messages, + "temperature": temperature, + } + if max_tokens is not None: + payload["max_tokens"] = max_tokens + + result = make_omlx_request("/chat/completions", data=payload, method="POST") + + choices = result.get("choices", []) + if not choices: + return "Ответ от oMLX пуст или не содержит вариантов (choices)." + + message_content = choices[0].get("message", {}).get("content", "") + return str(message_content) + + +def tool_check_local_model_status() -> str: + """ + Проверяет статус подключения к oMLX и возвращает список доступных локальных моделей. + + :return: Строка с отчетом о доступности сервера и списке загруженных моделей. + """ + try: + result = make_omlx_request("/models", method="GET") + data = result.get("data", []) + model_ids = [m.get("id", "unknown") for m in data if isinstance(m, dict)] + models_str = ", ".join(model_ids) if model_ids else "Модели не обнаружены в ответе" + return f"Сервер oMLX доступен по адресу {DEFAULT_OMLX_BASE_URL}. Доступные модели: {models_str}." + except Exception as error: + return f"Проверка статуса завершилась ошибкой: {str(error)}" + + +def tool_review_code_locally(code_snippet: str, instructions: str = "") -> str: + """ + Выполняет локальный анализ и ревью фрагмента кода силами oMLX. + + :param code_snippet: Исходный код для анализа. + :param instructions: Дополнительные требования к проверке (например, поиск багов, оптимизация). + :return: Замечания и рекомендации локальной модели. + """ + sys_prompt = ( + "Ты — опытный локальный ассистент по ревью кода. " + "Проанализируй предоставленный фрагмент кода, найди потенциальные ошибки, " + "узкие места и предложи конкретные улучшения без лишних вводных слов." + ) + user_prompt = f"Код для ревью:\n```\n{code_snippet}\n```" + if instructions: + user_prompt += f"\n\nСпециальные указания:\n{instructions}" + + return tool_ask_local_model(prompt=user_prompt, system_prompt=sys_prompt, temperature=0.1) + + +# Спецификация поддерживаемых MCP-инструментов +MCP_TOOLS: List[Dict[str, Any]] = [ + { + "name": "ask_local_model", + "description": ( + "Отправляет запрос локальной модели oMLX (на Apple Silicon) и возвращает ответ. " + "Используется для делегирования локальных вычислений, обработки больших текстов, " + "генерации вариантов или сохранения приватности." + ), + "inputSchema": { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "Основной текст запроса или задачи для локальной модели.", + }, + "system_prompt": { + "type": "string", + "description": "Опциональная системная инструкция для локальной модели.", + }, + "model": { + "type": "string", + "description": "Имя конкретной модели oMLX. Если не указано, берется активная модель.", + }, + "temperature": { + "type": "number", + "description": "Температура генерации (по умолчанию 0.2).", + }, + "max_tokens": { + "type": "integer", + "description": "Максимальное количество токенов в ответе.", + }, + }, + "required": ["prompt"], + }, + }, + { + "name": "check_local_model_status", + "description": "Проверяет доступность локального сервера oMLX и возвращает список моделей.", + "inputSchema": { + "type": "object", + "properties": {}, + }, + }, + { + "name": "review_code_locally", + "description": "Запускает локальное ревью фрагмента кода на модели oMLX.", + "inputSchema": { + "type": "object", + "properties": { + "code_snippet": { + "type": "string", + "description": "Фрагмент кода для анализа.", + }, + "instructions": { + "type": "string", + "description": "Особые пожелания или фокус внимания при анализе.", + }, + "model": { + "type": "string", + "description": "Имя конкретной модели oMLX.", + }, + }, + "required": ["code_snippet"], + }, + }, +] + + +def send_response(response: Dict[str, Any]) -> None: + """ + Отправляет JSON-RPC ответ в стандартный поток вывода stdout. + + :param response: Словарь с ответом по спецификации JSON-RPC 2.0. + """ + body = json.dumps(response, ensure_ascii=False) + sys.stdout.write(body + "\n") + sys.stdout.flush() + + +def handle_request(message: Dict[str, Any]) -> None: + """ + Обрабатывает входящее JSON-RPC сообщение протокола MCP. + + :param message: Распарсенное входящее сообщение. + """ + msg_id = message.get("id") + method = message.get("method") + params = message.get("params", {}) + + # Уведомления без id (например, notifications/initialized) не требуют ответа + if msg_id is None: + return + + if method == "initialize": + # Ответ на инициализацию сервера MCP + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": "omlx-local-bridge", + "version": "1.0.0" + } + } + }) + return + + if method == "ping": + send_response({"jsonrpc": "2.0", "id": msg_id, "result": {}}) + return + + if method == "tools/list": + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "result": { + "tools": MCP_TOOLS + } + }) + return + + if method == "tools/call": + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + try: + if tool_name == "ask_local_model": + max_tokens_val = arguments.get("max_tokens") + output = tool_ask_local_model( + prompt=arguments.get("prompt", ""), + system_prompt=arguments.get("system_prompt", ""), + model=arguments.get("model", ""), + temperature=float(arguments.get("temperature", 0.2)), + max_tokens=int(max_tokens_val) if max_tokens_val is not None else None, + ) + elif tool_name == "check_local_model_status": + output = tool_check_local_model_status() + elif tool_name == "review_code_locally": + output = tool_review_code_locally( + code_snippet=arguments.get("code_snippet", ""), + instructions=arguments.get("instructions", ""), + ) + else: + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "error": { + "code": -32601, + "message": f"Неизвестный инструмент: {tool_name}" + } + }) + return + + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "result": { + "content": [ + { + "type": "text", + "text": output + } + ], + "isError": False + } + }) + except Exception as error: + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "result": { + "content": [ + { + "type": "text", + "text": f"Ошибка выполнения инструмента {tool_name}: {str(error)}" + } + ], + "isError": True + } + }) + return + + # Неподдерживаемый метод + send_response({ + "jsonrpc": "2.0", + "id": msg_id, + "error": { + "code": -32601, + "message": f"Метод не поддерживается: {method}" + } + }) + + +def main() -> None: + """Главный цикл чтения сообщений из stdin.""" + try: + for line in sys.stdin: + clean_line = line.strip() + if not clean_line: + continue + try: + message = json.loads(clean_line) + handle_request(message) + except json.JSONDecodeError: + # Игнорируем некорректные JSON-строки + continue + except Exception as error: + # Логируем фатальные сбои в stderr + sys.stderr.write(f"Ошибка MCP сервера: {str(error)}\n") + sys.stderr.flush() + except KeyboardInterrupt: + # Корректное завершение работы при нажатии Ctrl+C пользователем в терминале + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..d542a67 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "34e39677d8527182346093002688d17a5d2fc204b9eb3e094b2e6ac519028228" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ce0ee62 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "2026-omlx-mcp-server" +version = "0.1.0" +description = "" +authors = ["erjemin "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"