--- doc_id: DEV-202606-004 feature_id: FEAT-202606-003-sqlite-his-mock-mcp-tools type: dev-progress title: SQLite HIS Mock MCP Tools Implementation Plan status: implemented owner: 医梦研发团队 created_at: 2026-06-04 updated_at: 2026-06-04 reviewers: [] related_docs: - PRD-202606-001 - DEV-202606-003 related_modules: - emoon-ai-mcp - emoon-openplatform tags: - SQLite - HIS Mock - MCP --- # SQLite HIS Mock MCP Tools Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build the demo HIS interface set inside the AI platform process, backed by SQLite, so registration demo workflows can call controlled MCP Tool APIs for patient, department, doctor, schedule, slot lock, mock payment, and appointment creation. **Architecture:** Implement the mock HIS adapter in `emoon-ai-mcp`, because HIS Mock/Real Adapter belongs to MCP per the engineering constraints. Expose the v1.2 standard Tool API (`GET /tools/catalog`, `POST /tools/{toolName}/invoke`) and keep existing `/api/mcp/his/tools/*` endpoints as compatibility wrappers. Use an isolated SQLite `DataSource` and `JdbcTemplate` instead of the platform MySQL/dynamic datasource. **Tech Stack:** Java 17, Spring Boot 3, Maven, JUnit 5, AssertJ, Mockito, SQLite JDBC, Spring `JdbcTemplate`. --- ## Required Documents Before Coding Read these completely before touching code: - `docs/standards/AI中台工程约束.md` - `docs/standards/工程规约生效说明.md` - `docs/initiatives/FEAT-202606-001-unified-entry-client/对外接口联调基准.md` - `docs/api/openapi/医梦AI中台开放平台接口-v1.2.openapi.yaml` - `docs/initiatives/FEAT-202606-001-unified-entry-client/需求文档.md` - `docs/initiatives/FEAT-202606-001-unified-entry-client/dev-progress/统一入口挂号演示实施计划.md` - `docs/initiatives/FEAT-202606-001-unified-entry-client/dev-progress/演示闭环纵向任务拆分.md` Important constraints: - Ordinary terminal clients must not call Tool APIs directly. - Tool APIs are for controlled adapters/internal services. - HIS writes must be idempotent. - Registration/payment demo responses must always carry an explicit Mock marker. - Do not put this implementation in `emoon-openplatform`, `emoon-ai-agent`, or `emoon-ai-card`. - Do not connect SQLite to the AI platform MySQL/dynamic datasource. ## Interface Scope Implement these v1.2-compatible endpoints: | Method | Path | Purpose | | --- | --- | --- | | `GET` | `/tools/catalog` | Return authorized demo tool catalog | | `POST` | `/tools/{toolName}/invoke` | Invoke a controlled HIS/payment demo tool | Keep these compatibility endpoints by delegating to the new invoke service: | Method | Path | | --- | --- | | `POST` | `/api/mcp/his/tools/get_departments` | | `POST` | `/api/mcp/his/tools/get_doctors` | | `POST` | `/api/mcp/his/tools/get_doctor_schedules` | | `POST` | `/api/mcp/his/tools/create_registration` | | `POST` | `/api/mcp/his/tools/invoke` | Tool names to implement: | Tool | Risk | Type | Demo Use | | --- | --- | --- | --- | | `his.searchPatient` | `L2_SENSITIVE_READ` | read | Find patient before filing | | `his.createPatient` | `L3_BUSINESS_WRITE` | write | Create demo patient file | | `his.queryDepartments` | `L1_QUERY` | read | Department recommendation card | | `his.queryDoctors` | `L1_QUERY` | read | Doctor selection card | | `his.querySchedules` | `L1_QUERY` | read | Time slot selection card | | `his.lockSchedule` | `L3_BUSINESS_WRITE` | write | Lock selected slot for 5 minutes | | `his.releaseSchedule` | `L3_BUSINESS_WRITE` | write | Release slot lock | | `payment.createOrder` | `L4_FINANCIAL_ACTION` | write | Create Mock payment QR payload | | `payment.queryStatus` | `L4_FINANCIAL_ACTION` | read | Read Mock payment status | | `payment.markMockPaid` | `L4_FINANCIAL_ACTION` | write | Demo-only simulated payment success | | `his.createRegistration` | `L3_BUSINESS_WRITE` | write | Create appointment after payment | | `his.queryRegistration` | `L2_SENSITIVE_READ` | read | Read appointment success detail | ## File Structure ### Maven - Modify: `pom.xml` - Add property `sqlite-jdbc.version`. - Add dependencyManagement entry for `org.xerial:sqlite-jdbc`. - Modify: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/pom.xml` - Add dependency `org.xerial:sqlite-jdbc`. ### SQLite Configuration - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/config/HisMockProperties.java` - Holds `enabled`, `jdbcUrl`, `resetOnStart`, `lockTtlSeconds`. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/config/HisMockSqliteConfig.java` - Creates isolated `hisMockDataSource` and `hisMockJdbcTemplate`. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/config/HisMockDatabaseInitializer.java` - Runs `schema.sql` and `seed.sql`. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/resources/db/his-mock/schema.sql` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/resources/db/his-mock/seed.sql` ### Domain and Tool DTO - Modify: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/client/HisClient.java` - Extend from existing query/create methods to demo registration flow methods. - Create/modify domain files under: - `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/domain/` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/tool/dto/ToolInvokeRequest.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/tool/dto/ToolContext.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/tool/dto/ToolInvokeData.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/tool/dto/ToolCatalogItem.java` These DTOs must match the OpenAPI schema: ```json { "toolCallId": "tool-call-001", "context": { "projectId": "demo-project", "cardInstanceId": "card-001", "traceId": "trace-001" }, "input": {}, "riskLevel": "L1_QUERY" } ``` Response data shape: ```json { "toolCallId": "tool-call-001", "toolName": "his.queryDepartments", "status": "success", "result": { "mock": true } } ``` ### Application Services - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/client/SqliteMockHisClient.java` - SQLite implementation of `HisClient`. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/application/HisToolCatalogService.java` - Returns tool metadata and schemas. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/application/HisToolInvokeService.java` - Validates tool name/risk/idempotency and dispatches to `HisClient`. - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/application/HisMockIdempotencyService.java` - Persists idempotency results for write tools. Keep `McpToolService` as the internal facade used by existing agent/card code, but delegate to `SqliteMockHisClient` instead of in-memory data. ### Controllers - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/web/ToolController.java` - `GET /tools/catalog` - `POST /tools/{toolName}/invoke` - Modify: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/main/java/com/emoon/mcp/his/web/HisMcpController.java` - Delegate to `HisToolInvokeService`. - Keep old endpoint response wrapper `McpToolResponse`. ### Tests - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/mcp/his/client/SqliteMockHisClientTest.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/mcp/his/application/HisToolCatalogServiceTest.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/mcp/his/application/HisToolInvokeServiceTest.java` - Create: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/mcp/his/web/ToolControllerTest.java` - Modify: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/ai/mcp/application/McpToolServiceTest.java` ## SQLite Schema Use these tables: ```sql CREATE TABLE IF NOT EXISTS his_patient ( patient_id TEXT PRIMARY KEY, patient_name TEXT NOT NULL, id_card TEXT, phone TEXT, gender TEXT, birth_date TEXT, created_at TEXT NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS uk_his_patient_id_card ON his_patient(id_card) WHERE id_card IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_his_patient_phone ON his_patient(phone); CREATE TABLE IF NOT EXISTS his_department ( department_id TEXT PRIMARY KEY, department_name TEXT NOT NULL, description TEXT, location TEXT, sort_no INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS his_doctor ( doctor_id TEXT PRIMARY KEY, department_id TEXT NOT NULL, doctor_name TEXT NOT NULL, title TEXT NOT NULL, specialty TEXT, consultation_fee INTEGER NOT NULL, FOREIGN KEY(department_id) REFERENCES his_department(department_id) ); CREATE TABLE IF NOT EXISTS his_schedule ( schedule_id TEXT PRIMARY KEY, doctor_id TEXT NOT NULL, schedule_date TEXT NOT NULL, time_period TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, total_count INTEGER NOT NULL, remaining_count INTEGER NOT NULL, fee INTEGER NOT NULL, FOREIGN KEY(doctor_id) REFERENCES his_doctor(doctor_id) ); CREATE TABLE IF NOT EXISTS his_slot_lock ( lock_id TEXT PRIMARY KEY, schedule_id TEXT NOT NULL, patient_id TEXT NOT NULL, status TEXT NOT NULL, expires_at TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS his_payment_order ( order_id TEXT PRIMARY KEY, lock_id TEXT NOT NULL, amount INTEGER NOT NULL, status TEXT NOT NULL, qr_content TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL, paid_at TEXT ); CREATE TABLE IF NOT EXISTS his_appointment ( appointment_id TEXT PRIMARY KEY, appointment_no TEXT NOT NULL UNIQUE, patient_id TEXT NOT NULL, department_id TEXT NOT NULL, doctor_id TEXT NOT NULL, schedule_id TEXT NOT NULL, lock_id TEXT NOT NULL, order_id TEXT NOT NULL, status TEXT NOT NULL, queue_no TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS his_idempotency_record ( idempotency_key TEXT PRIMARY KEY, tool_name TEXT NOT NULL, request_hash TEXT NOT NULL, response_json TEXT NOT NULL, created_at TEXT NOT NULL ); ``` Seed at least: - 3 departments: `dept_neurology`, `dept_digestive`, `dept_general` - 4 doctors across these departments - Schedule rows for today + 1 day and today + 2 days - A demo patient `patient_demo_001` ## Task 1: Add SQLite Dependency and Configuration **Files:** - Modify: `pom.xml` - Modify: `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/pom.xml` - Create: `.../config/HisMockProperties.java` - Create: `.../config/HisMockSqliteConfig.java` - [ ] **Step 1: Add failing configuration test** Create `emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp/src/test/java/com/emoon/mcp/his/config/HisMockSqliteConfigTest.java`: ```java package com.emoon.mcp.his.config; import org.junit.jupiter.api.Test; import org.springframework.jdbc.core.JdbcTemplate; import static org.assertj.core.api.Assertions.assertThat; class HisMockSqliteConfigTest { @Test void createsJdbcTemplateForSqliteDatasource() { HisMockProperties properties = new HisMockProperties(); properties.setJdbcUrl("jdbc:sqlite::memory:"); properties.setEnabled(true); properties.setLockTtlSeconds(300); HisMockSqliteConfig config = new HisMockSqliteConfig(); JdbcTemplate jdbcTemplate = config.hisMockJdbcTemplate(config.hisMockDataSource(properties)); Integer result = jdbcTemplate.queryForObject("select 1", Integer.class); assertThat(result).isEqualTo(1); } } ``` - [ ] **Step 2: Run failing test** Run: ```bash mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp -DskipTests=false -Dtest=HisMockSqliteConfigTest test ``` Expected: FAIL because config classes and SQLite dependency do not exist. - [ ] **Step 3: Add minimal implementation** Add `sqlite-jdbc.version` and dependencyManagement in root `pom.xml`, then dependency in `emoon-ai-mcp/pom.xml`. Create `HisMockProperties`: ```java package com.emoon.mcp.his.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @Data @ConfigurationProperties(prefix = "emoon.his.mock") public class HisMockProperties { private boolean enabled = true; private String jdbcUrl = "jdbc:sqlite:file:./data/his-mock.db"; private boolean resetOnStart = false; private long lockTtlSeconds = 300; } ``` Create `HisMockSqliteConfig`: ```java package com.emoon.mcp.his.config; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; @Configuration @EnableConfigurationProperties(HisMockProperties.class) public class HisMockSqliteConfig { @Bean public DataSource hisMockDataSource(HisMockProperties properties) { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.sqlite.JDBC"); dataSource.setUrl(properties.getJdbcUrl()); return dataSource; } @Bean public JdbcTemplate hisMockJdbcTemplate(DataSource hisMockDataSource) { return new JdbcTemplate(hisMockDataSource); } } ``` - [ ] **Step 4: Run test** Run the same Maven command. Expected: PASS. ## Task 2: Initialize SQLite Schema and Seed Data **Files:** - Create: `.../config/HisMockDatabaseInitializer.java` - Create: `.../resources/db/his-mock/schema.sql` - Create: `.../resources/db/his-mock/seed.sql` - Test: `.../config/HisMockDatabaseInitializerTest.java` - [ ] **Step 1: Write failing initializer test** Test must create an in-memory SQLite datasource, run initializer, then assert table and seed row exist: ```java Integer departments = jdbcTemplate.queryForObject("select count(*) from his_department", Integer.class); assertThat(departments).isGreaterThanOrEqualTo(3); ``` - [ ] **Step 2: Run failing test** ```bash mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp -DskipTests=false -Dtest=HisMockDatabaseInitializerTest test ``` Expected: FAIL because initializer and SQL resources do not exist. - [ ] **Step 3: Implement initializer** Use `ResourceDatabasePopulator` with `schema.sql` and `seed.sql`. If `resetOnStart=true`, run a small drop script before schema creation. Keep default `resetOnStart=false`. - [ ] **Step 4: Run test** Expected: PASS. ## Task 3: Implement Query Tools **Files:** - Modify: `HisClient.java` - Create: `SqliteMockHisClient.java` - Modify/create domain classes under `.../domain` - Test: `SqliteMockHisClientTest.java` - [ ] **Step 1: Write failing tests** Cover: - `queryDepartments` returns seeded departments with `mock=true` available in wrapper result. - `queryDoctors("dept_neurology")` returns neurologists. - `querySchedules(doctorId, date)` returns remaining count and fee. - unknown department/doctor returns empty list, not exception. - [ ] **Step 2: Run failing tests** ```bash mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp -DskipTests=false -Dtest=SqliteMockHisClientTest test ``` - [ ] **Step 3: Implement SQLite reads** Use `JdbcTemplate.query(...)`; do not add MyBatis Mapper for this SQLite mock. - [ ] **Step 4: Run tests** Expected: PASS. ## Task 4: Implement Patient Filing Tools **Files:** - Modify: `HisClient.java` - Modify: `SqliteMockHisClient.java` - Add domain: `HisPatient.java` - Test: `SqliteMockHisClientTest.java` - [ ] **Step 1: Write failing tests** Cover: - `searchPatient` finds by phone. - `searchPatient` finds by id card. - `createPatient` creates a patient and returns same patient for duplicate idempotency key. - duplicate id card returns existing patient instead of creating a second row. - [ ] **Step 2: Run failing tests** Expected: FAIL. - [ ] **Step 3: Implement patient methods** Use `his_patient` and `his_idempotency_record`. Return: ```json { "patientId": "patient_xxx", "patientName": "张三", "mock": true } ``` - [ ] **Step 4: Run tests** Expected: PASS. ## Task 5: Implement Slot Lock and Release **Files:** - Modify: `HisClient.java` - Modify: `SqliteMockHisClient.java` - Add domain: `HisSlotLock.java` - Test: `SqliteMockHisClientTest.java` - [ ] **Step 1: Write failing tests** Cover: - lock succeeds when `remaining_count > 0`. - lock returns same result for duplicate idempotency key. - lock fails when schedule has no remaining slots. - release changes lock status to `RELEASED`. - expired locks do not block new lock attempts. - [ ] **Step 2: Run failing tests** Expected: FAIL. - [ ] **Step 3: Implement lock/release** Use a transaction. For demo SQLite, synchronize around write methods in `SqliteMockHisClient` to avoid double-lock in same JVM. Do not decrement `remaining_count` on lock. Decrement only when appointment is created. - [ ] **Step 4: Run tests** Expected: PASS. ## Task 6: Implement Mock Payment Tools **Files:** - Modify: `HisClient.java` - Modify: `SqliteMockHisClient.java` - Add domain: `HisPaymentOrder.java` - Test: `SqliteMockHisClientTest.java` - [ ] **Step 1: Write failing tests** Cover: - create payment order requires active lock. - order amount comes from schedule fee. - QR content starts with `demo-payment://orderId=`. - duplicate create order idempotency key returns same order. - `markMockPaid` changes status to `PAID`. - payment result includes `mock=true` and `mockPayment=true`. - [ ] **Step 2: Run failing tests** Expected: FAIL. - [ ] **Step 3: Implement payment methods** Statuses: ```text PENDING PAID CANCELLED ``` Do not use WeChat, Alipay,医保, or any real payment brand in response fields. - [ ] **Step 4: Run tests** Expected: PASS. ## Task 7: Implement Appointment Creation **Files:** - Modify: `HisClient.java` - Modify: `SqliteMockHisClient.java` - Modify: `HisRegistrationRequest.java` - Modify: `HisRegistrationResult.java` - Test: `SqliteMockHisClientTest.java` - [ ] **Step 1: Write failing tests** Cover: - appointment creation requires paid order. - appointment decrements `remaining_count`. - duplicate idempotency key returns same appointment. - unpaid order fails and does not decrement stock. - response includes `appointmentId`, `appointmentNo`, `queueNo`, `mock=true`. - [ ] **Step 2: Run failing tests** Expected: FAIL. - [ ] **Step 3: Implement appointment method** Use `his_appointment`. Generate: ```text appointmentId = appt_<12 chars> appointmentNo = REGyyyyMMdd queueNo = A01/A02... ``` - [ ] **Step 4: Run tests** Expected: PASS. ## Task 8: Implement Tool Catalog **Files:** - Create: `HisToolCatalogService.java` - Create: `ToolCatalogItem.java` - Test: `HisToolCatalogServiceTest.java` - [ ] **Step 1: Write failing catalog test** Assert catalog contains exactly the scoped demo tools from this plan and each item has: - `toolName` - `toolType` - `description` - `inputSchema` - `outputSchema` - `riskLevel` - [ ] **Step 2: Run failing test** Expected: FAIL. - [ ] **Step 3: Implement catalog** Keep schemas small but concrete. Example: ```json { "toolName": "his.queryDoctors", "toolType": "read", "riskLevel": "L1_QUERY", "inputSchema": { "type": "object", "required": ["departmentId"], "properties": { "departmentId": {"type": "string"} } } } ``` - [ ] **Step 4: Run test** Expected: PASS. ## Task 9: Implement Tool Invoke Service **Files:** - Create: `HisToolInvokeService.java` - Create: `ToolInvokeRequest.java` - Create: `ToolContext.java` - Create: `ToolInvokeData.java` - Test: `HisToolInvokeServiceTest.java` - [ ] **Step 1: Write failing invoke tests** Cover: - unknown tool returns `status=failed`. - `toolCallId` is echoed. - successful read tool returns `status=success`. - write tool without idempotency key returns `status=failed`. - risk mismatch returns `status=forbidden`. - `payment.markMockPaid` is allowed only because result has explicit `mockPayment=true`. - [ ] **Step 2: Run failing test** Expected: FAIL. - [ ] **Step 3: Implement dispatcher** Dispatch by path `toolName`, not request body `toolName`, to match OpenAPI: ```java public ToolInvokeData invoke(String toolName, String idempotencyKey, ToolInvokeRequest request) ``` Read idempotency key from: 1. `X-Emoon-Idempotency-Key` header 2. `request.input.idempotencyKey` Write tools must have one of them. - [ ] **Step 4: Run test** Expected: PASS. ## Task 10: Add Standard Tool API Controller **Files:** - Create: `ToolController.java` - Test: `ToolControllerTest.java` - [ ] **Step 1: Write failing controller tests** Use `@WebMvcTest(ToolController.class)` or direct controller test depending on project test stability. Assert: - `GET /tools/catalog` returns `R.ok` style response with `data.tools`. - `POST /tools/his.queryDepartments/invoke` accepts OpenAPI `ToolInvokeRequest`. - header `X-Emoon-Idempotency-Key` is passed to invoke service. - [ ] **Step 2: Run failing test** Expected: FAIL. - [ ] **Step 3: Implement controller** Controller methods: ```java @GetMapping("/tools/catalog") public R> catalog() @PostMapping("/tools/{toolName}/invoke") public R invoke( @PathVariable String toolName, @RequestHeader(value = "X-Emoon-Idempotency-Key", required = false) String idempotencyKey, @Valid @RequestBody ToolInvokeRequest request) ``` - [ ] **Step 4: Run test** Expected: PASS. ## Task 11: Update Existing HIS MCP Compatibility Controller **Files:** - Modify: `HisMcpController.java` - Modify: old tool DTOs if needed - Test: `ToolControllerTest.java` or `HisMcpControllerTest.java` - [ ] **Step 1: Write failing compatibility tests** Cover old calls: - `/api/mcp/his/tools/get_departments` - `/api/mcp/his/tools/get_doctors` - `/api/mcp/his/tools/get_doctor_schedules` - `/api/mcp/his/tools/create_registration` - `/api/mcp/his/tools/invoke` with old `toolName` values - [ ] **Step 2: Run failing tests** Expected: FAIL after service replacement until compatibility mapping is done. - [ ] **Step 3: Implement compatibility mapping** Map old names: | Old | New | | --- | --- | | `his_get_departments` | `his.queryDepartments` | | `his_get_doctors` | `his.queryDoctors` | | `his_get_doctor_schedules` | `his.querySchedules` | | `his_create_registration` | `his.createRegistration` | For old `create_registration`, if there is no paid order in request, return a clear failed demo response rather than silently bypassing payment. If compatibility with existing tests requires success, add an explicit demo-only path with generated lock/order paid in one transaction and response `mock=true`. - [ ] **Step 4: Run tests** Expected: PASS. ## Task 12: Update Internal `McpToolService` **Files:** - Modify: `McpToolService.java` - Modify: `McpToolServiceTest.java` - [ ] **Step 1: Update failing tests** Replace mock expectations so: - `lockSchedule` calls real `HisClient.lockSchedule(...)` style method. - `createAppointment` includes `orderId` or delegates to the new registration flow. - returned map still contains `mock=true`. - [ ] **Step 2: Run failing test** Expected: FAIL. - [ ] **Step 3: Implement facade update** Keep existing public methods if used by agent/card code, but route through new client methods. Do not duplicate business rules here. - [ ] **Step 4: Run tests** Expected: PASS. ## Task 13: Add Demo Curl Scripts **Files:** - Create: `scripts/demo-registration/tools-catalog.sh` - Create: `scripts/demo-registration/registration-tool-flow.sh` - [ ] **Step 1: Write scripts** The flow script must call: ```text GET /tools/catalog POST /tools/his.searchPatient/invoke POST /tools/his.queryDepartments/invoke POST /tools/his.queryDoctors/invoke POST /tools/his.querySchedules/invoke POST /tools/his.lockSchedule/invoke POST /tools/payment.createOrder/invoke POST /tools/payment.markMockPaid/invoke POST /tools/his.createRegistration/invoke ``` - [ ] **Step 2: Include expected response markers** Each write step should echo the idempotency key and expected response marker: ```text mock=true mockPayment=true for payment tools status=success ``` ## Task 14: Verification - [ ] **Step 1: Run MCP module compile** ```bash mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp -am -DskipTests compile ``` Expected: BUILD SUCCESS. - [ ] **Step 2: Run MCP module tests** ```bash mvn -pl emoon-infra/emoon-modules/emoon-ai/emoon-ai-mcp -DskipTests=false test ``` Expected: BUILD SUCCESS. - [ ] **Step 3: Run architecture test** ```bash mvn -pl emoon-admin -DskipTests=false -Dprofiles.active= -Dtest=AiPlatformArchitectureTest test ``` Expected: BUILD SUCCESS. - [ ] **Step 4: Manual smoke test after startup** Start AI platform normally, then call: ```bash curl -s http://localhost:8080/tools/catalog ``` Expected: response contains `his.queryDepartments`, `his.createRegistration`, and `payment.createOrder`. ## Acceptance Criteria - AI platform starts with SQLite Mock HIS enabled by default in demo/dev. - `/tools/catalog` returns the demo HIS/payment catalog using v1.2 `ToolCatalogItem` shape. - `/tools/{toolName}/invoke` accepts v1.2 `ToolInvokeRequest` shape. - Query tools work without idempotency key. - Write tools require idempotency key. - Duplicate write calls return the original result. - Slot lock expires after configured TTL. - Payment is clearly Mock and does not reference real payment brands. - Appointment creation requires paid mock order. - Appointment creation decrements remaining schedule count exactly once. - Old `/api/mcp/his/tools/*` endpoints still work as compatibility wrappers. - No Controller or Mapper for this mock is added to `emoon-openplatform`. - No SQLite datasource is added to the platform MySQL dynamic datasource.