{"_id":"@conkhidan/tms-mcp","name":"@conkhidan/tms-mcp","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@conkhidan/tms-mcp","version":"0.1.0","description":"MCP server for TMS / Timesheet system — exposes timesheet & OT operations as MCP tools for AI clients (Claude Desktop, Cursor)","type":"module","main":"dist/index.js","bin":{"tms-mcp":"dist/index.js"},"scripts":{"build":"tsc && node -e \"import('node:fs').then(({chmodSync,readFileSync,writeFileSync})=>{const p='dist/index.js';const s=readFileSync(p,'utf8');if(!s.startsWith('#!'))writeFileSync(p,'#!/usr/bin/env node\\n'+s);chmodSync(p,0o755);})\"","dev":"tsx src/index.ts","start":"node dist/index.js","test":"vitest run","test:e2e":"vitest run --config /dev/null tests/e2e","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm test && npm run build"},"keywords":["mcp","model-context-protocol","tms","timesheet","overtime","claude","cursor","ai-tools"],"author":{"name":"longdv"},"license":"MIT","engines":{"node":">=18"},"publishConfig":{"access":"public"},"dependencies":{"@modelcontextprotocol/sdk":"^1.21.1","cheerio":"^1.0.0","zod":"^3.24.4","zod-to-json-schema":"^3.25.2"},"devDependencies":{"@types/node":"^22.15.17","dotenv":"^17.4.2","tsx":"^4.19.4","typescript":"^5.8.3","vitest":"^3.1.4"},"_id":"@conkhidan/tms-mcp@0.1.0","gitHead":"0a8534ca0e1d9aaac6febd059eac0ded5790baf4","types":"./dist/index.d.ts","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-yPe4Th4X+V0kgm/wR666hAnnlD+mqpEf5WUqR0mBUVyY9FGQiEirLVuWIuWNMW0nJmahj4hEifeXlAngtmvb3Q==","shasum":"8f8e63cf8e413944dd8ecd6e6fcfda285b3d34bf","tarball":"https://registry.npmjs.org/@conkhidan/tms-mcp/-/tms-mcp-0.1.0.tgz","fileCount":67,"unpackedSize":137689,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDSak60KZ/IXIkzo4zOHO+EyTk7VHM+juu6RkOzAFMpYAiACFE0g8RHfqOVCQFEbyM1EAWPm1Qy5UtfQnLbpfWyt8Q=="}]},"_npmUser":{"name":"conkhidan","email":"dinhlong.utt@gmail.com"},"directories":{},"maintainers":[{"name":"conkhidan","email":"dinhlong.utt@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tms-mcp_0.1.0_1779120582675_0.01707165313066006"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-18T16:09:42.556Z","0.1.0":"2026-05-18T16:09:42.847Z","modified":"2026-05-18T16:09:43.034Z"},"maintainers":[{"name":"conkhidan","email":"dinhlong.utt@gmail.com"}],"description":"MCP server for TMS / Timesheet system — exposes timesheet & OT operations as MCP tools for AI clients (Claude Desktop, Cursor)","keywords":["mcp","model-context-protocol","tms","timesheet","overtime","claude","cursor","ai-tools"],"author":{"name":"longdv"},"license":"MIT","readme":"# TMS MCP Server\n\nMCP (Model Context Protocol) server cho hệ thống TMS / Timesheet. Cho phép AI client (Claude, Cursor, v.v.) thao tác với TMS thông qua MCP tools thay vì thao tác UI thủ công.\n\n## Luồng hoạt động\n\n```\nAI Client → MCP Tool → Timesheet Service → TMS HTML Client → TMS Server\n                ↑                                    ↓\n                └──── TMS HTML Parser ←────── HTML Response\n```\n\nMCP login, giữ session, parse HTML form, submit form-urlencoded, parse HTML response, trả object sạch cho AI.\n\n## Cài đặt\n\n**Cách 1 — Dùng trực tiếp qua npx (không cần clone):**\n\n```bash\nnpx -y @longdv/tms-mcp\n```\n\n**Cách 2 — Cài global:**\n\n```bash\nnpm install -g @longdv/tms-mcp\ntms-mcp\n```\n\n**Cách 3 — Clone & dev:**\n\n```bash\ngit clone <repo-url>\ncd tms-mcp\nnpm install\n```\n\n## Cấu hình\n\nCopy `.env.example` → `.env` và điền thông tin:\n\n```env\n# Bắt buộc\nTMS_BASE_URL=http://tms.dvcqg.click:8080\n\n# Auth - chọn 1 trong 2 cách:\n# Cách 1: Username + Password (MCP sẽ tự login)\nTMS_USERNAME=your_username\nTMS_PASSWORD=your_password\nTMS_REMEMBER=1\n\n# Cách 2: Session cookie có sẵn\n# TMS_SESSION_COOKIE=laravel_session=xxx;XSRF-TOKEN=xxx\n\n# Tuỳ chọn\nTMS_REQUEST_TIMEOUT_MS=30000\nTMS_USER_AGENT=Mozilla/5.0 (compatible; TMS-MCP/0.1)\n\n# Transport (mặc định: stdio)\n# MCP_TRANSPORT=http\n# MCP_PORT=3001\n```\n\n**Chạy dev:**\n\n```bash\nnpm run dev\n```\n\n**Build và chạy production:**\n\n```bash\nnpm run build && npm start\n```\n\n**Chạy ở chế độ HTTP (Streamable HTTP transport):**\n\n```bash\nMCP_TRANSPORT=http MCP_PORT=3001 npm start\n```\n\nMặc định server dùng **stdio** (cho Claude Desktop / Cursor spawn child process). Đặt `MCP_TRANSPORT=http` để chạy như HTTP server (cho remote / multi-host).\n\n## Kết nối với AI client\n\n### Claude Desktop\n\nThêm vào `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"tms\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@longdv/tms-mcp\"],\n      \"env\": {\n        \"TMS_BASE_URL\": \"http://tms.dvcqg.click:8080\",\n        \"TMS_USERNAME\": \"your_username\",\n        \"TMS_PASSWORD\": \"your_password\"\n      }\n    }\n  }\n}\n```\n\n> Nếu đã `npm install -g @longdv/tms-mcp`, có thể thay `\"command\": \"tms-mcp\"` và bỏ `args`.\n\n### Cursor\n\nThêm vào `.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"tms\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@longdv/tms-mcp\"],\n      \"env\": {\n        \"TMS_BASE_URL\": \"http://tms.dvcqg.click:8080\",\n        \"TMS_USERNAME\": \"your_username\",\n        \"TMS_PASSWORD\": \"your_password\"\n      }\n    }\n  }\n}\n```\n\n## Tools\n\n### 1. `create_timesheet_log`\n\nTạo mới timesheet log.\n\n**Input:**\n\n```json\n{\n  \"workDate\": \"2026-05-14\",\n  \"hours\": 1,\n  \"title\": \"Tạo dữ liệu phân quyền cho QC/BA\",\n  \"projectId\": \"4\",\n  \"stageId\": \"103\",\n  \"content\": \"Mô tả công việc\",\n  \"deadline\": \"2026-05-20\",\n  \"meetingId\": \"0\",\n  \"redmineIssuesId\": \"0\",\n  \"redmineLogtimeId\": \"0\",\n  \"redmineType\": \"1\",\n  \"answerFor\": \"0\",\n  \"sourceType\": \"0\"\n}\n```\n\n| Field | Required | Mặc định | Format |\n|-------|----------|----------|--------|\n| `workDate` | ✅ | | `YYYY-MM-DD` |\n| `hours` | ✅ | | `0.01 - 24` |\n| `title` | ✅ | | |\n| `projectId` | ✅ | | |\n| `stageId` | ✅ | | |\n| `content` | ✅ | | |\n| `deadline` | ❌ | | `YYYY-MM-DD` |\n| `meetingId` | ❌ | `\"0\"` | |\n| `redmineIssuesId` | ❌ | `\"0\"` | |\n| `redmineLogtimeId` | ❌ | `\"0\"` | |\n| `redmineType` | ❌ | `\"1\"` | |\n| `answerFor` | ❌ | `\"0\"` | |\n| `sourceType` | ❌ | `\"0\"` | |\n\n**Output success:**\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Created timesheet log successfully\",\n  \"data\": {\n    \"workDate\": \"2026-05-14\",\n    \"tmsDate\": \"14/05/2026\",\n    \"hours\": 1,\n    \"title\": \"Tạo dữ liệu phân quyền cho QC/BA\",\n    \"projectId\": \"4\",\n    \"stageId\": \"103\",\n    \"content\": \"Mô tả công việc\"\n  }\n}\n```\n\n**Output lỗi validation:**\n\n```json\n{\n  \"success\": false,\n  \"message\": \"Validation failed\",\n  \"errors\": [\"The hours field is required.\"]\n}\n```\n\n### 2. `update_timesheet_log`\n\nCập nhật timesheet log theo ID.\n\n**Input:**\n\n```json\n{\n  \"id\": \"10663\",\n  \"hours\": 3,\n  \"title\": \"CSDLQGTTHC-1372: Refactor permission group tree\",\n  \"projectId\": \"4\",\n  \"stageId\": \"103\",\n  \"content\": \"Simplify buildPermissionGroupTree to leaf nodes\"\n}\n```\n\n| Field | Required | Format |\n|-------|----------|--------|\n| `id` | ✅ | ID của log cần sửa |\n| `hours` | ✅ | `0.01 - 24` |\n| `title` | ✅ | |\n| `projectId` | ✅ | |\n| `stageId` | ✅ | |\n| `content` | ✅ | |\n\n**Output success:**\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Updated timesheet log successfully\",\n  \"data\": {\n    \"id\": \"10663\",\n    \"hours\": 3,\n    \"title\": \"CSDLQGTTHC-1372: Refactor permission group tree\",\n    \"projectId\": \"4\",\n    \"stageId\": \"103\",\n    \"content\": \"Simplify buildPermissionGroupTree to leaf nodes\"\n  }\n}\n```\n\n### 3. `get_my_timesheet_logs`\n\nLấy danh sách timesheet logs.\n\n**Input:**\n\n```json\n{\n  \"fromDate\": \"2026-05-01\",\n  \"toDate\": \"2026-05-14\"\n}\n```\n\n| Field | Required | Format |\n|-------|----------|--------|\n| `fromDate` | ❌ | `YYYY-MM-DD` |\n| `toDate` | ❌ | `YYYY-MM-DD` |\n\n**Output:**\n\n```json\n{\n  \"success\": true,\n  \"logs\": [\n    {\n      \"id\": \"10663\",\n      \"workDate\": \"14-05-2026\",\n      \"hours\": 1,\n      \"title\": \"E2E test log by MCP\",\n      \"projectName\": \"CSDL Dùng chung Quốc gia về TTHC\",\n      \"stageName\": \"Coding\",\n      \"content\": \"E2E test: verifying MCP creates timesheet\",\n      \"status\": \"Dữ liệu tự khai\"\n    }\n  ]\n}\n```\n\n### 4. `get_available_projects`\n\nLấy danh sách project từ TMS.\n\n**Input (tuỳ chọn):**\n\n```json\n{ \"workDate\": \"2026-05-14\" }\n```\n\n**Output:**\n\n```json\n{\n  \"success\": true,\n  \"projects\": [\n    { \"id\": \"1\", \"name\": \"Dịch vụ công Quốc gia\" },\n    { \"id\": \"4\", \"name\": \"CSDL Dùng chung Quốc gia về TTHC\" },\n    { \"id\": \"9\", \"name\": \"Daily\" }\n  ]\n}\n```\n\n### 5. `get_available_stages`\n\nLấy danh sách stage cho một project.\n\n**Input:**\n\n```json\n{\n  \"projectId\": \"4\"\n}\n```\n\n| Field | Required | Mặc định |\n|-------|----------|----------|\n| `projectId` | ❌ | `\"4\"` |\n\n**Output:**\n\n```json\n{\n  \"success\": true,\n  \"stages\": [\n    { \"id\": \"3\", \"name\": \"Customer support\" },\n    { \"id\": \"5\", \"name\": \"Detail Design\" },\n    { \"id\": \"103\", \"name\": \"Coding\" },\n    { \"id\": \"105\", \"name\": \"Testing\" }\n  ]\n}\n```\n\n## Error handling\n\n### Session hết hạn\n\n```json\n{\n  \"success\": false,\n  \"message\": \"TMS session expired or unauthorized\"\n}\n```\n\n### Không parse được HTML\n\n```json\n{\n  \"success\": false,\n  \"message\": \"Unable to parse TMS HTML. Selector may need update\"\n}\n```\n\n### Validation error\n\n```json\n{\n  \"success\": false,\n  \"message\": \"Validation error\",\n  \"errors\": [\"workDate: workDate must be in YYYY-MM-DD format\"]\n}\n```\n\n## Lưu ý bảo mật\n\n- **Tuyệt đối không commit `.env` file** — đã có `.gitignore`\n- **Không hard-code credentials** trong code, test, fixture, hay README\n- **Không log password, cookie, CSRF token** ra console\n- **Dùng account riêng cho AI** nếu có thể (scope giới hạn)\n- **Rotate password** nếu nghi ngờ bị lộ session\n\n## Chạy test\n\n```bash\n# Tất cả tests (unit + smoke)\nnpm test\n\n# Chỉ E2E (cần .env với credentials thật)\nnpx vitest run tests/e2e\n\n# Typecheck\nnpm run typecheck\n```\n\n## Cấu trúc code\n\n```\nsrc/\n  index.ts                          # Entry point\n  config.ts                         # Env config\n\n  mcp/\n    server.ts                       # MCP server setup, tool registration\n    tools/\n      create-timesheet-log.tool.ts\n      update-timesheet-log.tool.ts\n      get-my-timesheet-logs.tool.ts\n      get-available-projects.tool.ts\n      get-available-stages.tool.ts\n\n  timesheet/\n    timesheet.service.ts            # Use-case orchestration\n    timesheet.types.ts              # TypeScript interfaces\n    timesheet-form.builder.ts       # Form URL-encoded builder\n    date-format.ts                  # YYYY-MM-DD → dd/MM/yyyy\n    validation.ts                   # Zod schemas\n\n  tms-html/\n    tms-html.client.ts              # HTTP client (fetch + cookie)\n    tms-session.manager.ts          # Login, session lifecycle\n    tms-html.parser.ts              # Cheerio HTML parsing\n    tms-selectors.ts                # CSS selectors config\n    cookie-jar.ts                   # In-memory cookie jar\n\ntests/\n  fixtures/                         # HTML samples for parser tests\n  e2e/                              # End-to-end tests (real server)\n```\n\n## Cập nhật selectors khi HTML TMS thay đổi\n\nTất cả selectors nằm trong `src/tms-html/tms-selectors.ts`. Khi TMS thay đổi HTML:\n\n1. Mở TMS UI thật để xem HTML mới\n2. Cập nhật selector trong `tms-selectors.ts`\n3. Chạy `npm test` để verify parser vẫn hoạt động\n4. Nếu cần, cập nhật fixtures trong `tests/fixtures/`\n\n## Giới hạn hiện tại\n\n- **Stage**: Phải fetch qua AJAX endpoint (`/ajax/liststage?project_id=X`), không có sẵn trong form tạo\n- **List table**: Parse dựa trên column index cố định. Không có `projectId`/`stageId` trong table (chỉ có name)\n- **Date filter**: TMS `/tms` không hỗ trợ query params lọc theo date → filter client-side\n- **Empty rows**: Mặc định gửi 1 row. Nếu TMS yêu cầu nhiều row như UI, set `TMS_EMPTY_ROWS_COUNT`\n","readmeFilename":"README.md","_rev":"1-242c686b712679739fdeabc1a4f07454"}