Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export class RedmineMCPServer {
* @param args.subject - Search text in issue subject/title
* @returns Promise resolving to formatted issue data
*/
private async getIssues(
public async getIssues(
args: GetIssuesArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -414,7 +414,7 @@ export class RedmineMCPServer {
* @param args.issue_id - The ID of the issue to retrieve
* @returns Promise resolving to formatted issue data
*/
private async getIssueById(
public async getIssueById(
args: GetIssueByIdArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -442,7 +442,7 @@ export class RedmineMCPServer {
* @param args.name - Search for projects containing this name (case-insensitive)
* @returns Promise resolving to formatted project data with ID mappings
*/
private async getProjects(
public async getProjects(
args: GetProjectsArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -495,7 +495,7 @@ export class RedmineMCPServer {
* @param args.priority_id - Priority ID for the issue
* @returns Promise resolving to created issue data
*/
private async createIssue(
public async createIssue(
args: CreateIssueArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -546,7 +546,7 @@ export class RedmineMCPServer {
* @param args.notes - Notes to add to the issue history
* @returns Promise resolving to success confirmation
*/
private async updateIssue(
public async updateIssue(
args: UpdateIssueArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -603,7 +603,7 @@ export class RedmineMCPServer {
* @param args.limit - Maximum number of time entries to return
* @returns Promise resolving to formatted time entries data
*/
private async getTimeEntries(
public async getTimeEntries(
args: GetTimeEntriesArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -639,7 +639,7 @@ export class RedmineMCPServer {
* @param args.project_id - Project ID to get project-specific activities (optional)
* @returns Promise resolving to available time tracking activities
*/
private async getTimeActivities(
public async getTimeActivities(
args: GetTimeActivitiesArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -708,7 +708,7 @@ export class RedmineMCPServer {
* @param args.spent_on - Date when time was spent (YYYY-MM-DD format)
* @returns Promise resolving to time entry creation confirmation
*/
private async logTime(
public async logTime(
args: LogTimeArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down Expand Up @@ -761,7 +761,7 @@ export class RedmineMCPServer {
* @param args - Arguments for getting current user (optional include parameter)
* @returns Promise resolving to current user information
*/
private async getCurrentUser(
public async getCurrentUser(
args: GetCurrentUserArgs,
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
try {
Expand Down
61 changes: 23 additions & 38 deletions test/e2e/mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest";
/**
* End-to-end test for Redmine MCP Server
* Tests the MCP server against a real Redmine instance running in Docker
*
* Note: Dynamic imports are used to allow setting environment variables
* before the config module is loaded.
*/

/**
Expand Down Expand Up @@ -144,6 +147,8 @@ describe("Redmine MCP Server E2E", () => {
// Setup: Start Redmine and get API key
containerName = await startRedmineContainer();
apiKey = await loginAndGetApiKey(baseUrl);
process.env["REDMINE_URL"] = baseUrl;
process.env["REDMINE_API_KEY"] = apiKey;
}, 120000); // 2 minute timeout for container startup

afterAll(async () => {
Expand All @@ -154,50 +159,30 @@ describe("Redmine MCP Server E2E", () => {
});

it("should create MCP server instance successfully", async () => {
// Set environment variables for MCP server
const originalUrl = process.env["REDMINE_URL"];
const originalKey = process.env["REDMINE_API_KEY"];

process.env["REDMINE_URL"] = baseUrl;
process.env["REDMINE_API_KEY"] = apiKey;
const { RedmineMCPServer } = await import("../../src/index.js");
const server = new RedmineMCPServer();

try {
// Dynamically import MCP server after environment variables are set
const { RedmineMCPServer } = await import("../../src/index.js");
const server = new RedmineMCPServer();

expect(server).toBeDefined();
} finally {
// Restore original environment variables
if (originalUrl !== undefined) {
process.env["REDMINE_URL"] = originalUrl;
} else {
delete process.env["REDMINE_URL"];
}

if (originalKey !== undefined) {
process.env["REDMINE_API_KEY"] = originalKey;
} else {
delete process.env["REDMINE_API_KEY"];
}
}
expect(server).toBeDefined();
});

it("should get current user via Redmine API", async () => {
// Test get_current_user using the Redmine API directly
// Since we can't easily call MCP tools directly without the full protocol,
// we'll verify the API works by making a direct HTTP call
const response = await fetch(`${baseUrl}/users/current.json`, {
headers: {
"X-Redmine-API-Key": apiKey,
},
});
it("should get current user via MCP server method", async () => {
const { RedmineMCPServer } = await import("../../src/index.js");
const server = new RedmineMCPServer();

// Call the getCurrentUser method directly
const response = await server.getCurrentUser({});

expect(response.ok).toBe(true);
// Verify MCP response structure
expect(response).toHaveProperty("content");
expect(Array.isArray(response.content)).toBe(true);
expect(response.content.length).toBeGreaterThan(0);
expect(response.content[0]).toHaveProperty("type", "text");
expect(response.content[0]).toHaveProperty("text");

const userData = await response.json();
// Parse the JSON response
const userData = JSON.parse(response.content[0]!.text);

// Assertions using Vitest's expect API
// Assertions on the returned user data
expect(userData).toHaveProperty("user");
expect(userData.user).toHaveProperty("login", "admin");
expect(userData.user).toHaveProperty("id");
Expand Down