diff --git a/docs/TODO.md b/docs/TODO.md index 96b7d1b..3043d05 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -60,18 +60,18 @@ - [x] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`). - [x] Add service tests in `tests/services.test.ts` (unit tests and live container integration suite passing). -- [ ] **Task 3: MCP Tool Definitions & Schema Registration** *(In Progress: Tasks 1-3 of plan completed)* - - [ ] Define Zod schemas and register tools in `src/tools/`: +- [x] **Task 3: MCP Tool Definitions & Schema Registration** + - [x] Define Zod schemas and register tools in `src/tools/`: - [x] `openproject_list_projects` - [x] `openproject_get_project` - [x] `openproject_list_work_packages` - [x] `openproject_get_work_package` - - [ ] `openproject_list_queries` - - [ ] `openproject_get_query` - - [ ] `openproject_list_types` - - [ ] `openproject_list_statuses` - - [ ] `openproject_list_priorities` - - [ ] `openproject_list_users` + - [x] `openproject_list_queries` + - [x] `openproject_get_query` + - [x] `openproject_list_types` + - [x] `openproject_list_statuses` + - [x] `openproject_list_priorities` + - [x] `openproject_list_users` - [ ] **Task 4: MCP Stdio Server Assembly & Integration** - [ ] Wire domain tools into `@modelcontextprotocol/sdk` Server using `StdioServerTransport` in `src/server.ts` and `src/index.ts`. diff --git a/tests/tools.test.ts b/tests/tools.test.ts index 1c6af3f..4315d04 100644 --- a/tests/tools.test.ts +++ b/tests/tools.test.ts @@ -51,7 +51,7 @@ import { OpenProjectNotFoundError } from "../src/client/errors"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { runWithContext } from "../src/context"; -import type { OpenProjectClient } from "../src/client/api-client"; +import { OpenProjectClient } from "../src/client/api-client"; describe("Tool Utilities", () => { @@ -989,5 +989,86 @@ describe("Tool Registry", () => { }); }); +describe("Live Container Integration (All 10 MCP Tools)", () => { + const baseUrl = process.env.OPENPROJECT_BASE_URL || "http://localhost:8080"; + const apiKey = process.env.OPENPROJECT_API_KEY || ""; + const liveClient = new OpenProjectClient({ baseUrl, apiKey }); + + test("live: list projects and get project tool execution", async () => { + await runWithContext({ client: liveClient, isReadOnly: false }, async () => { + const listRes = await handleListProjects({ pageSize: 5 }); + expect(listRes.isError).toBeUndefined(); + const listData = JSON.parse(listRes.content[0].text); + expect(listData.projects.length).toBeGreaterThan(0); + + const targetProject = + listData.projects.find( + (p: { identifier?: string; id: number }) => p.identifier === "mcp-test-project" + ) || listData.projects[0]; + const getRes = await handleGetProject({ projectId: targetProject.id }); + expect(getRes.isError).toBeUndefined(); + const getData = JSON.parse(getRes.content[0].text); + expect(getData.id).toBe(targetProject.id); + }); + }); + + test("live: list work packages and get work package tool execution", async () => { + await runWithContext({ client: liveClient, isReadOnly: false }, async () => { + const listRes = await handleListWorkPackages({ pageSize: 5 }); + expect(listRes.isError).toBeUndefined(); + const listData = JSON.parse(listRes.content[0].text); + expect(listData.workPackages.length).toBeGreaterThan(0); + + const wpId = listData.workPackages[0].id; + const getRes = await handleGetWorkPackage({ workPackageId: wpId }); + expect(getRes.isError).toBeUndefined(); + const getData = JSON.parse(getRes.content[0].text); + expect(getData.id).toBe(wpId); + expect(getData.subject).toBeDefined(); + }); + }); + + test("live: list queries and get query tool execution", async () => { + await runWithContext({ client: liveClient, isReadOnly: false }, async () => { + const listRes = await handleListQueries({}); + expect(listRes.isError).toBeUndefined(); + const listData = JSON.parse(listRes.content[0].text); + expect(listData.queries.length).toBeGreaterThan(0); + + const queryId = listData.queries[0].id; + const getRes = await handleGetQuery({ queryId }); + expect(getRes.isError).toBeUndefined(); + const getData = JSON.parse(getRes.content[0].text); + expect(getData.query.id).toBe(queryId); + expect(getData.results).toBeDefined(); + }); + }); + + test("live: metadata tools execution (types, statuses, priorities, users)", async () => { + await runWithContext({ client: liveClient, isReadOnly: false }, async () => { + const typesRes = await handleListTypes({}); + expect(typesRes.isError).toBeUndefined(); + const typesData = JSON.parse(typesRes.content[0].text); + expect(typesData.types.length).toBeGreaterThan(0); + + const statusesRes = await handleListStatuses({}); + expect(statusesRes.isError).toBeUndefined(); + const statusesData = JSON.parse(statusesRes.content[0].text); + expect(statusesData.statuses.length).toBeGreaterThan(0); + + const prioritiesRes = await handleListPriorities({}); + expect(prioritiesRes.isError).toBeUndefined(); + const prioritiesData = JSON.parse(prioritiesRes.content[0].text); + expect(prioritiesData.priorities.length).toBeGreaterThan(0); + + const usersRes = await handleListUsers({ pageSize: 5 }); + expect(usersRes.isError).toBeUndefined(); + const usersData = JSON.parse(usersRes.content[0].text); + expect(usersData.users.length).toBeGreaterThan(0); + }); + }); +}); + +