test(openapi): add live container tests, update tool count assertions, and update TODO.md
This commit is contained in:
@@ -58,6 +58,7 @@
|
|||||||
- [x] Implement `src/services/work-packages.ts` (`listWorkPackages`, `getWorkPackage`, `searchWorkPackages`).
|
- [x] Implement `src/services/work-packages.ts` (`listWorkPackages`, `getWorkPackage`, `searchWorkPackages`).
|
||||||
- [x] Implement `src/services/queries.ts` (`listQueries`, `getQuery`, `getQueryResults`).
|
- [x] Implement `src/services/queries.ts` (`listQueries`, `getQuery`, `getQueryResults`).
|
||||||
- [x] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`).
|
- [x] Implement `src/services/metadata.ts` (`listStatuses`, `listTypes`, `listPriorities`, `listUsers`).
|
||||||
|
- [x] Implement `src/services/openapi.ts` (`getOpenApiSpec` with in-memory caching, summary mode, path endpoint lookup, tag filtering, schema inspection).
|
||||||
- [x] Add service tests in `tests/services.test.ts` (unit tests and live container integration suite passing).
|
- [x] Add service tests in `tests/services.test.ts` (unit tests and live container integration suite passing).
|
||||||
|
|
||||||
- [x] **Task 3: MCP Tool Definitions & Schema Registration**
|
- [x] **Task 3: MCP Tool Definitions & Schema Registration**
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
- [x] `openproject_list_statuses`
|
- [x] `openproject_list_statuses`
|
||||||
- [x] `openproject_list_priorities`
|
- [x] `openproject_list_priorities`
|
||||||
- [x] `openproject_list_users`
|
- [x] `openproject_list_users`
|
||||||
|
- [x] `openproject_get_openapi_spec`
|
||||||
|
|
||||||
- [x] **Task 4: MCP Stdio Server Assembly & Integration**
|
- [x] **Task 4: MCP Stdio Server Assembly & Integration**
|
||||||
- [x] Wire domain tools into `@modelcontextprotocol/sdk` Server using `StdioServerTransport` in `src/server.ts` and `src/index.ts`.
|
- [x] Wire domain tools into `@modelcontextprotocol/sdk` Server using `StdioServerTransport` in `src/server.ts` and `src/index.ts`.
|
||||||
@@ -80,6 +82,12 @@
|
|||||||
- [x] Add end-to-end integration tests in `tests/mcp-server.test.ts` verifying tool calling against the running OpenProject 17 instance.
|
- [x] Add end-to-end integration tests in `tests/mcp-server.test.ts` verifying tool calling against the running OpenProject 17 instance.
|
||||||
- [x] Add read-only mode verification tests in `tests/read-only.test.ts`.
|
- [x] Add read-only mode verification tests in `tests/read-only.test.ts`.
|
||||||
|
|
||||||
|
- [x] **Task 5: OpenAPI Introspection Tool & Service**
|
||||||
|
- [x] Implement `src/services/openapi.ts` with in-memory caching, multi-mode queries (summary, path, tag, schema), and ambient client resolution.
|
||||||
|
- [x] Implement `src/tools/openapi.ts` registering `openproject_get_openapi_spec` (`readOnly: true`) into `allTools` (bringing tool count to 11).
|
||||||
|
- [x] Add comprehensive test suite in `tests/openapi.test.ts` covering service methods, MCP client execution, error handling, and live OpenProject 17 container integration.
|
||||||
|
- [x] Update suite regressions and assertions across all test suites (`tests/tools.test.ts`, `tests/read-only.test.ts`, `tests/mcp-server.test.ts`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Backlog & Future Phases
|
## 3. Backlog & Future Phases
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|||||||
import { createServer, SERVER_NAME, SERVER_VERSION } from "../src/server";
|
import { createServer, SERVER_NAME, SERVER_VERSION } from "../src/server";
|
||||||
|
|
||||||
describe("MCP Server Factory", () => {
|
describe("MCP Server Factory", () => {
|
||||||
test("creates server and lists all 10 tools via InMemoryTransport", async () => {
|
test("creates server and lists all 11 tools via InMemoryTransport", async () => {
|
||||||
const config = {
|
const config = {
|
||||||
baseUrl: "http://localhost:8080",
|
baseUrl: "http://localhost:8080",
|
||||||
apiKey: "test-key",
|
apiKey: "test-key",
|
||||||
@@ -21,7 +21,7 @@ describe("MCP Server Factory", () => {
|
|||||||
await client.connect(clientTransport);
|
await client.connect(clientTransport);
|
||||||
|
|
||||||
const toolsResult = await client.listTools();
|
const toolsResult = await client.listTools();
|
||||||
expect(toolsResult.tools).toHaveLength(10);
|
expect(toolsResult.tools).toHaveLength(11);
|
||||||
const names = toolsResult.tools.map((t) => t.name);
|
const names = toolsResult.tools.map((t) => t.name);
|
||||||
expect(names).toContain("openproject_list_projects");
|
expect(names).toContain("openproject_list_projects");
|
||||||
expect(names).toContain("openproject_get_project");
|
expect(names).toContain("openproject_get_project");
|
||||||
@@ -33,6 +33,7 @@ describe("MCP Server Factory", () => {
|
|||||||
expect(names).toContain("openproject_list_statuses");
|
expect(names).toContain("openproject_list_statuses");
|
||||||
expect(names).toContain("openproject_list_priorities");
|
expect(names).toContain("openproject_list_priorities");
|
||||||
expect(names).toContain("openproject_list_users");
|
expect(names).toContain("openproject_list_users");
|
||||||
|
expect(names).toContain("openproject_get_openapi_spec");
|
||||||
|
|
||||||
await client.close();
|
await client.close();
|
||||||
await mcpServer.stop();
|
await mcpServer.stop();
|
||||||
@@ -135,7 +136,7 @@ describe("MCP Server Factory", () => {
|
|||||||
|
|
||||||
// In read-only mode, only readOnly: true tools should be registered
|
// In read-only mode, only readOnly: true tools should be registered
|
||||||
const toolsResult = await client.listTools();
|
const toolsResult = await client.listTools();
|
||||||
expect(toolsResult.tools).toHaveLength(10);
|
expect(toolsResult.tools).toHaveLength(11);
|
||||||
|
|
||||||
await client.close();
|
await client.close();
|
||||||
await mcpServer.stop();
|
await mcpServer.stop();
|
||||||
|
|||||||
@@ -238,3 +238,32 @@ describe("OpenApi MCP Tool Registration & Execution", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Live Container OpenAPI Integration", () => {
|
||||||
|
const liveBaseUrl = process.env.OPENPROJECT_BASE_URL || "http://localhost:8080";
|
||||||
|
const liveApiKey = process.env.OPENPROJECT_API_KEY || "";
|
||||||
|
const liveClient = new OpenProjectClient({ baseUrl: liveBaseUrl, apiKey: liveApiKey });
|
||||||
|
|
||||||
|
test("live query returns OpenProject API summary with paths and tags", async () => {
|
||||||
|
const summary = (await getOpenApiSpec({}, liveClient)) as OpenApiSummary;
|
||||||
|
expect(summary.title).toContain("OpenProject API");
|
||||||
|
expect(summary.totalPaths).toBeGreaterThan(200);
|
||||||
|
expect(summary.tags.length).toBeGreaterThan(30);
|
||||||
|
expect(summary.availablePaths).toContain("/api/v3/work_packages");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("live query fetches operation details for /api/v3/work_packages", async () => {
|
||||||
|
const res = (await getOpenApiSpec({ path: "/api/v3/work_packages" }, liveClient)) as {
|
||||||
|
path: string;
|
||||||
|
operations: {
|
||||||
|
get?: {
|
||||||
|
parameters?: Array<{ name: string }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
expect(res.operations.get).toBeDefined();
|
||||||
|
const paramNames = res.operations.get?.parameters?.map((p) => p.name) ?? [];
|
||||||
|
expect(paramNames).toContain("offset");
|
||||||
|
expect(paramNames).toContain("pageSize");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe("Read-Only Mode Enforcement", () => {
|
|||||||
expect(configFalse.readOnly).toBe(false);
|
expect(configFalse.readOnly).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("read-only server lists all 10 Phase 1 tools (all are readOnly: true)", async () => {
|
test("read-only server lists all 11 tools (all are readOnly: true)", async () => {
|
||||||
const mcpServer = createServer({
|
const mcpServer = createServer({
|
||||||
baseUrl: "http://localhost:8080",
|
baseUrl: "http://localhost:8080",
|
||||||
apiKey: "test-key",
|
apiKey: "test-key",
|
||||||
@@ -87,7 +87,7 @@ describe("Read-Only Mode Enforcement", () => {
|
|||||||
await client.connect(clientTransport);
|
await client.connect(clientTransport);
|
||||||
|
|
||||||
const toolsResult = await client.listTools();
|
const toolsResult = await client.listTools();
|
||||||
expect(toolsResult.tools).toHaveLength(10);
|
expect(toolsResult.tools).toHaveLength(11);
|
||||||
|
|
||||||
await client.close();
|
await client.close();
|
||||||
await mcpServer.stop();
|
await mcpServer.stop();
|
||||||
@@ -118,7 +118,7 @@ describe("Read-Only Mode Enforcement", () => {
|
|||||||
const standaloneTools = await client2.listTools();
|
const standaloneTools = await client2.listTools();
|
||||||
const standaloneNames = standaloneTools.tools.map((t) => t.name);
|
const standaloneNames = standaloneTools.tools.map((t) => t.name);
|
||||||
expect(standaloneNames).not.toContain("openproject_create_work_package_synthetic");
|
expect(standaloneNames).not.toContain("openproject_create_work_package_synthetic");
|
||||||
expect(standaloneTools.tools).toHaveLength(10);
|
expect(standaloneTools.tools).toHaveLength(11);
|
||||||
|
|
||||||
await client2.close();
|
await client2.close();
|
||||||
await standaloneServer.close();
|
await standaloneServer.close();
|
||||||
|
|||||||
+6
-5
@@ -955,8 +955,8 @@ describe("Metadata Tools", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Tool Registry", () => {
|
describe("Tool Registry", () => {
|
||||||
test("allTools contains exactly 10 Phase 1 tools", () => {
|
test("allTools contains exactly 11 tools including OpenAPI spec", () => {
|
||||||
expect(allTools).toHaveLength(10);
|
expect(allTools).toHaveLength(11);
|
||||||
const names = allTools.map((t) => t.name);
|
const names = allTools.map((t) => t.name);
|
||||||
expect(names).toEqual([
|
expect(names).toEqual([
|
||||||
"openproject_list_projects",
|
"openproject_list_projects",
|
||||||
@@ -969,6 +969,7 @@ describe("Tool Registry", () => {
|
|||||||
"openproject_list_statuses",
|
"openproject_list_statuses",
|
||||||
"openproject_list_priorities",
|
"openproject_list_priorities",
|
||||||
"openproject_list_users",
|
"openproject_list_users",
|
||||||
|
"openproject_get_openapi_spec",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -978,7 +979,7 @@ describe("Tool Registry", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("registerAllTools registers all 10 tools on McpServer", () => {
|
test("registerAllTools registers all 11 tools on McpServer", () => {
|
||||||
const server = new McpServer({ name: "test-mcp", version: "1.0.0" });
|
const server = new McpServer({ name: "test-mcp", version: "1.0.0" });
|
||||||
registerAllTools(server);
|
registerAllTools(server);
|
||||||
|
|
||||||
@@ -988,7 +989,7 @@ describe("Tool Registry", () => {
|
|||||||
}
|
}
|
||||||
)._registeredTools;
|
)._registeredTools;
|
||||||
|
|
||||||
expect(Object.keys(registeredTools)).toHaveLength(10);
|
expect(Object.keys(registeredTools)).toHaveLength(11);
|
||||||
for (const tool of allTools) {
|
for (const tool of allTools) {
|
||||||
expect(registeredTools[tool.name]).toBeDefined();
|
expect(registeredTools[tool.name]).toBeDefined();
|
||||||
}
|
}
|
||||||
@@ -1020,7 +1021,7 @@ describe("Tool Registry", () => {
|
|||||||
expect(
|
expect(
|
||||||
registeredTools["openproject_synthetic_mutating_tool"]
|
registeredTools["openproject_synthetic_mutating_tool"]
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
expect(Object.keys(registeredTools)).toHaveLength(10);
|
expect(Object.keys(registeredTools)).toHaveLength(11);
|
||||||
for (const tool of allTools) {
|
for (const tool of allTools) {
|
||||||
expect(registeredTools[tool.name]).toBeDefined();
|
expect(registeredTools[tool.name]).toBeDefined();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user