add: начальная реализация универсального MCP-сервера для локальных LLM

- Реализован 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 <junie@jetbrains.com>
This commit is contained in:
2026-10-15 11:33:16 +03:00
co-authored by Junie
commit c18c71c6fc
8 changed files with 1415 additions and 0 deletions
+7
View File
@@ -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
+716
View File
@@ -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/
+16
View File
@@ -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 — экспертом по проверке фрагментов кода и генерации идей.
* При недоступности или таймауте локальной модели продолжай работу самостоятельно, не блокируя задачу.
+2
View File
@@ -0,0 +1,2 @@
* Не делай git commit, git push и другие команды git, если специально не попросили. Если это нужно, предложи 2-3 варианта коммитов и сообщений к ним на русском языке.
* Префиксы коммитов: `fix:` — исправление ошибки; `add:` — новый файл, модуль, метод, функциональность; `mod:` — изменения, улучшения, развите существующих методов, классов и т.п.; `ref:` — рефакторинг; `doc:` — документация; `tst:` — тесты; `minor:` — незначительные изменения, стиль кода, комментарии, форматирование; `chore:` — вспомогательные задачи, не влияющие на функциональность (например, обновление зависимостей).
+215
View File
@@ -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`.
+438
View File
@@ -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()
Generated
+7
View File
@@ -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"
+14
View File
@@ -0,0 +1,14 @@
[tool.poetry]
name = "2026-omlx-mcp-server"
version = "0.1.0"
description = ""
authors = ["erjemin <erjemin@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"