Add docker-service stubs and ignore runtime artifacts.
This tracks compose and source files for mcp-stub/phoenix while excluding local runtime data and wheel artifacts. Made-with: Cursor
This commit is contained in:
@@ -19,3 +19,7 @@ htmlcov/
|
|||||||
|
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Runtime artifacts
|
||||||
|
*.whl
|
||||||
|
phoenix/data/
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./PySocks-1.7.1-py3-none-any.whl /app/PySocks-1.7.1-py3-none-any.whl
|
||||||
|
RUN pip install --no-cache-dir /app/PySocks-1.7.1-py3-none-any.whl
|
||||||
|
|
||||||
|
COPY requirements.txt /app/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||||
|
|
||||||
|
COPY app.py /app/app.py
|
||||||
|
|
||||||
|
EXPOSE 8081
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# MCP Stub Service
|
||||||
|
|
||||||
|
Минимальный MCP server (Streamable HTTP) для локальной интеграции.
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Сервис поднимется на `http://localhost:8081`.
|
||||||
|
MCP endpoint: `http://localhost:8081/mcp`.
|
||||||
|
|
||||||
|
## Инструменты MCP
|
||||||
|
|
||||||
|
- `search_news_sources(url: str)`
|
||||||
|
- `parse_article(url: str)`
|
||||||
|
- `extract_publication_date(article_text: str)`
|
||||||
|
- `rank_sources_by_date(items: list[dict])`
|
||||||
|
- `generate_summary(items: list[dict])`
|
||||||
|
|
||||||
|
Пример проверки через Python MCP client:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python - <<'PY'
|
||||||
|
import asyncio
|
||||||
|
from mcp import ClientSession
|
||||||
|
from mcp.client.streamable_http import streamablehttp_client
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with streamablehttp_client(url="http://localhost:8081/mcp") as (read, write, *_):
|
||||||
|
async with ClientSession(read, write) as session:
|
||||||
|
await session.initialize()
|
||||||
|
result = await session.call_tool("search_news_sources", {"url": "https://example.com/news"})
|
||||||
|
print(result.structuredContent)
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
PY
|
||||||
|
```
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
|
||||||
|
def _utc_now_iso() -> str:
|
||||||
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def _base_result(tool_name: str, ok: bool, payload: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"tool_name": tool_name,
|
||||||
|
"ok": ok,
|
||||||
|
"payload": payload,
|
||||||
|
"received_at": _utc_now_iso(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mcp = FastMCP(
|
||||||
|
"prisma_mcp_stub",
|
||||||
|
host=os.getenv("MCP_STUB_HOST", "0.0.0.0"),
|
||||||
|
port=int(os.getenv("MCP_STUB_PORT", "8081")),
|
||||||
|
streamable_http_path="/mcp",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def search_news_sources(url: str) -> dict[str, Any]:
|
||||||
|
return _base_result(
|
||||||
|
tool_name="search_news_sources",
|
||||||
|
ok=True,
|
||||||
|
payload={
|
||||||
|
"input_url": str(url),
|
||||||
|
"items": [
|
||||||
|
{"url": "https://news-a.example/article-1"},
|
||||||
|
{"url": "https://news-b.example/article-2"},
|
||||||
|
{"url": "https://news-c.example/article-3"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def parse_article(url: str) -> dict[str, Any]:
|
||||||
|
return _base_result(
|
||||||
|
tool_name="parse_article",
|
||||||
|
ok=True,
|
||||||
|
payload={
|
||||||
|
"url": str(url),
|
||||||
|
"title": "Stub article title",
|
||||||
|
"published_at": "2026-01-01T10:00:00+00:00",
|
||||||
|
"text": "Stub parsed article content.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def extract_publication_date(article_text: str) -> dict[str, Any]:
|
||||||
|
return _base_result(
|
||||||
|
tool_name="extract_publication_date",
|
||||||
|
ok=True,
|
||||||
|
payload={
|
||||||
|
"text_size": len(article_text),
|
||||||
|
"published_at": "2026-01-01T10:00:00+00:00",
|
||||||
|
"confidence": 0.77,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def rank_sources_by_date(items: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
ranked = sorted(items, key=lambda item: str(item.get("published_at", "")))
|
||||||
|
return _base_result(
|
||||||
|
tool_name="rank_sources_by_date",
|
||||||
|
ok=True,
|
||||||
|
payload={
|
||||||
|
"input_count": len(items),
|
||||||
|
"ranked_items": ranked,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def generate_summary(items: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
first_url = ""
|
||||||
|
if items:
|
||||||
|
first_url = str(items[0].get("url", ""))
|
||||||
|
|
||||||
|
return _base_result(
|
||||||
|
tool_name="generate_summary",
|
||||||
|
ok=True,
|
||||||
|
payload={
|
||||||
|
"input_count": len(items),
|
||||||
|
"summary": "По заглушечным данным самым ранним источником считается " + first_url,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mcp.run(transport="streamable-http")
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
mcp-stub:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: local-mcp-stub:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8081:8081"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
mcp
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PHOENIX_WORKING_DIR=/mnt/data
|
||||||
|
ENV PHOENIX_HOST=0.0.0.0
|
||||||
|
ENV PHOENIX_PORT=6006
|
||||||
|
ENV PHOENIX_GRPC_PORT=4317
|
||||||
|
|
||||||
|
COPY ./PySocks-1.7.1-py3-none-any.whl /app/PySocks-1.7.1-py3-none-any.whl
|
||||||
|
RUN pip install --no-cache-dir /app/PySocks-1.7.1-py3-none-any.whl
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir arize-phoenix
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN mkdir -p /mnt/data
|
||||||
|
VOLUME ["/mnt/data"]
|
||||||
|
|
||||||
|
EXPOSE 6006 4317
|
||||||
|
|
||||||
|
CMD ["phoenix", "serve"]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
services:
|
||||||
|
phoenix:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: local-phoenix:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6006:6006"
|
||||||
|
- "4317:4317"
|
||||||
|
environment:
|
||||||
|
PHOENIX_WORKING_DIR: /mnt/data
|
||||||
|
# Optional: use external Postgres instead of local SQLite
|
||||||
|
# PHOENIX_SQL_DATABASE_URL: postgresql://user:password@host:5432/dbname
|
||||||
|
volumes:
|
||||||
|
- ./phoenix-data:/mnt/data
|
||||||
Reference in New Issue
Block a user