{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-md-header",
   "metadata": {},
   "source": "# Crowd SDK MCP — Human MCP Demo\n\nTools for managing labeling project and task lifecycle on Tagme.\n\n## Tools covered\n- `get_human_mcp_markup_options` — browse available templates, pools, and existing projects\n- `summarize_projects_for_human_mcp` — inspect existing human_mcp projects (instruction, slug, pools)\n- `configure_human_mcp_project` — create a project from template or swap template on existing project\n- `create_markup_task_with_items` — create a task and upload labeling items\n- `generate_tagme_interface` — generate a labeling interface via tagme-x-genui service (multi-step dialog)\n- `apply_interface_to_project` — apply generated html/js/css/config/spec to a project's method\n- `list_project_tasks` — list all tasks in a project\n- `update_markup_task` — update task parameters (name, price, overlap, deadline, description)\n- `stop_markup_task` — stop a running task\n\n## MCP config example\n\n```json\n{\n  \"mcpServers\": {\n    \"crowd-sdk\": {\n      \"command\": \"tagme\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nFor this notebook, we connect to the same server directly over stdio."
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-imports",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "from pathlib import Path\n",
    "from typing import Any, Dict\n",
    "from urllib import error, request\n",
    "\n",
    "import ssl\n",
    "\n",
    "ssl._create_default_https_context = ssl._create_unverified_context\n",
    "\n",
    "from mcp import ClientSession, StdioServerParameters\n",
    "from mcp.client.stdio import stdio_client\n",
    "\n",
    "ENV_FILE = Path('/path/to/.env')\n",
    "MCP_COMMAND = 'tagme'\n",
    "MCP_ARGS = ['mcp']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-helpers",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_env(path: Path) -> None:\n",
    "    if not path.exists():\n",
    "        return\n",
    "\n",
    "    for raw_line in path.read_text(encoding='utf-8').splitlines():\n",
    "        line = raw_line.strip()\n",
    "        if not line or line.startswith('#') or '=' not in line:\n",
    "            continue\n",
    "\n",
    "        key, value = line.split('=', 1)\n",
    "        os.environ.setdefault(key.strip(), value.strip().strip('\"').strip(\"'\"))\n",
    "\n",
    "\n",
    "def server_params() -> StdioServerParameters:\n",
    "    load_env(ENV_FILE)\n",
    "    return StdioServerParameters(\n",
    "        command=MCP_COMMAND,\n",
    "        args=MCP_ARGS,\n",
    "        env=os.environ.copy(),\n",
    "        cwd=str(Path.cwd()),\n",
    "    )\n",
    "\n",
    "\n",
    "def payload_from_mcp_result(tool_result: Any) -> Any:\n",
    "    structured_content = getattr(tool_result, 'structuredContent', None) or {}\n",
    "    if 'result' in structured_content:\n",
    "        return structured_content['result']\n",
    "    return [content.model_dump(mode='json') for content in tool_result.content]\n",
    "\n",
    "\n",
    "async def call_tool(tool_name: str, arguments: Dict[str, Any] | None = None) -> Any:\n",
    "    async with stdio_client(server_params()) as (read, write):\n",
    "        async with ClientSession(read, write) as session:\n",
    "            await session.initialize()\n",
    "            result = await session.call_tool(tool_name, arguments or {})\n",
    "            return payload_from_mcp_result(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-md-browse",
   "metadata": {},
   "source": [
    "## 1. Browse markup options\n",
    "\n",
    "Before creating tasks, call `get_human_mcp_markup_options` to discover available templates, pools,\n",
    "and existing human_mcp projects. The returned `human_mcp_projects` list provides the `project_id`\n",
    "values you will need in the next step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-browse",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = await call_tool(\n",
    "    'get_human_mcp_markup_options',\n",
    "    {\n",
    "        # 'organization_id': 'your-org-id',  # optional\n",
    "        # 'config_path': None,\n",
    "    },\n",
    ")\n",
    "print(f\"Templates: {options.get('templates_returned_count')}\")\n",
    "print(f\"Pools: {len(options.get('pools', []))}\")\n",
    "print(f\"Human MCP projects: {len(options.get('human_mcp_projects', []))}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-md-create",
   "metadata": {},
   "source": [
    "## 2. Create a markup task\n",
    "\n",
    "`create_markup_task_with_items` requires an existing `project_id` (get one from\n",
    "`get_human_mcp_markup_options` → `human_mcp_projects`).\n",
    "\n",
    "Each item in `items` must have a `\"task\"` key whose fields match the labeling form of the target\n",
    "project. `start_after_upload=False` by default — the task is created and items are uploaded, but\n",
    "the task is not started."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-create-task",
   "metadata": {},
   "outputs": [],
   "source": [
    "PROJECT_ID = 'your-project-id'  # get from get_human_mcp_markup_options → human_mcp_projects\n",
    "\n",
    "sample_items = [\n",
    "    {'task': {'text': 'Пример текста для разметки 1', 'id': 'item-001'}},\n",
    "    {'task': {'text': 'Пример текста для разметки 2', 'id': 'item-002'}},\n",
    "    {'task': {'text': 'Пример текста для разметки 3', 'id': 'item-003'}},\n",
    "]\n",
    "\n",
    "created = await call_tool(\n",
    "    'create_markup_task_with_items',\n",
    "    {\n",
    "        'project_id': PROJECT_ID,\n",
    "        'task_name': 'Demo task via MCP',\n",
    "        'items': sample_items,\n",
    "        'price': 10.0,\n",
    "        'overlap': 1,\n",
    "        'start_after_upload': False,  # set True only if you want to start immediately\n",
    "    },\n",
    ")\n",
    "print(json.dumps(created, ensure_ascii=False, indent=2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "jt9g3c08adg",
   "source": "## 3. Generate interface via genui\n\n`generate_tagme_interface` calls a tagme-x-genui service to generate a labeling interface. It is a multi-step dialog — the service may ask clarifying questions before generating.\n\nOn first call pass `prompt` and `genui_url`. If `status=needs_input`, re-call with `generation_id` and `answers`. When `status=completed`, the `interface` field contains `html`, `js`, `css`, `config`, `specification`, `example`.",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "id": "tjt5plbqtlp",
   "source": "GENUI_URL = 'http://localhost:8000'  # URL of your tagme-x-genui service\n\ngen_result = await call_tool(\n    'generate_tagme_interface',\n    {\n        'prompt': 'Интерфейс для бинарной классификации текстов: нужно разметить тональность (позитив/негатив)',\n        'genui_url': GENUI_URL,\n    },\n)\nprint(f\"Status: {gen_result.get('status')}\")\nprint(f\"Generation ID: {gen_result.get('generation_id')}\")\nif gen_result.get('status') == 'needs_input':\n    print(\"Questions from genui:\")\n    for q in gen_result.get('questions', []):\n        print(f\"  [{q.get('question_id')}] {q.get('text')}\")\nelif gen_result.get('status') == 'completed':\n    iface = gen_result.get('interface', {})\n    print(\"Interface ready. Fields:\", [k for k, v in iface.items() if v])",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "id": "y6ale8cqdvh",
   "source": "# Run only if status == 'needs_input'\nGENERATION_ID = gen_result.get('generation_id')\n\ngen_result = await call_tool(\n    'generate_tagme_interface',\n    {\n        'prompt': 'Интерфейс для бинарной классификации текстов',\n        'genui_url': GENUI_URL,\n        'generation_id': GENERATION_ID,\n        'answers': [\n            {'question_id': 'q1', 'value': 'Да'},\n            # add more answers for each question returned in the previous step\n        ],\n    },\n)\nprint(f\"Status: {gen_result.get('status')}\")\nif gen_result.get('status') == 'completed':\n    print(\"Interface ready:\", list(gen_result.get('interface', {}).keys()))",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "m1v57ube6ke",
   "source": "## 4. Apply interface to project\n\n`apply_interface_to_project` writes generated html/js/css/config/spec/example to the project's labeling method. Pass the `interface` dict from `generate_tagme_interface` directly using `**` unpacking, or specify fields manually.",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "id": "whdfua7d9o",
   "source": "PROJECT_ID = 'your-project-id'\n\niface = gen_result.get('interface', {})  # from generate_tagme_interface result\n\napplied = await call_tool(\n    'apply_interface_to_project',\n    {\n        'project_id': PROJECT_ID,\n        **{k: v for k, v in iface.items() if v is not None},\n        # 'organization_id': 'your-org-id',  # optional\n    },\n)\nprint(f\"Applied fields: {applied.get('updated_fields')}\")",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "eim5jfhqyob",
   "source": "## 5. List project tasks\n\n`list_project_tasks` returns all tasks in a project (including training/exam tasks). Use this to find task IDs before updating or stopping.",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "id": "yp1zxomeg6m",
   "source": "tasks_result = await call_tool(\n    'list_project_tasks',\n    {\n        'project_id': PROJECT_ID,\n        # 'organization_id': 'your-org-id',  # optional\n    },\n)\nprint(f\"Tasks count: {tasks_result.get('tasks_count')}\")\nfor t in tasks_result.get('tasks', []):\n    print(f\"  [{t.get('state')}] {t.get('task_id')} — {t.get('name')}\")",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "grcrv4fzkd5",
   "source": "## 6. Update task parameters\n\n`update_markup_task` lets you change task parameters after creation. At least one of `name`, `price`, `overlap`, `deadline`, `description` must be provided. The task does not need to be stopped first.",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "id": "1cts170idh7",
   "source": "TASK_ID = 'your-task-id'  # from list_project_tasks\n\nupdated = await call_tool(\n    'update_markup_task',\n    {\n        'task_id': TASK_ID,\n        'price': 15.0,\n        'overlap': 2,\n        'deadline': '2026-07-01',\n        # 'name': 'Updated task name',\n        # 'description': 'Updated description',\n    },\n)\nprint(json.dumps(updated.get('task', {}), ensure_ascii=False, indent=2))",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "12fxr9nm8uqd",
   "source": "## 7. Stop a task\n\n`stop_markup_task` stops an active (running) task. The tool waits until the task is fully stopped before returning.",
   "metadata": {}
  },
  {
   "cell_type": "code",
   "id": "osof6vs3s7",
   "source": "stopped = await call_tool(\n    'stop_markup_task',\n    {\n        'task_id': TASK_ID,\n        # 'organization_id': 'your-org-id',  # optional\n    },\n)\nprint(f\"Task state after stop: {stopped.get('task', {}).get('state')}\")",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "jiz22fc42no",
   "source": "> **Note:** This tool is under active development. The item schema (`task` key structure) must\n> match the labeling form fields of the target project.",
   "metadata": {}
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}