Skip to content
Draft
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
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ A Model Context Protocol (MCP) server for managing Tempo worklogs in Jira. This
- **Bulk Create**: Create multiple worklogs in a single operation
- **Edit Worklog**: Modify time spent, dates, and descriptions
- **Delete Worklog**: Remove existing worklogs
- **Search User**: Find Jira users by name or email
- **Multi-User Support**: Manage worklogs for other team members

## System Requirements

Expand Down Expand Up @@ -191,21 +193,43 @@ To find your custom field ID:
1. Go to Jira Settings → Issues → Custom Fields
2. Find your Tempo account field and note the ID from the URL or field configuration

## Multi-User Support

The server supports managing worklogs for other team members. By default, all operations use the configured user, but you can specify a different user in two ways:

1. **By name or email** (`userName`): The server searches Jira for the user and resolves their account ID
2. **By account ID** (`accountId`): Directly specify the Jira account ID (takes precedence over `userName`)

Example: To create a worklog for a colleague, use `userName: "John Doe"` or `userName: "john@example.com"`.

Note: Your Tempo API token must have the necessary permissions to manage worklogs for other users.

## Available Tools

### searchUser

Searches for a Jira user by display name or email. Returns the user's account ID, which can be used with other tools.

```
Parameters:
- query: String (name or email to search for)
```

### retrieveWorklogs

Fetches worklogs for the configured user between start and end dates.
Fetches worklogs between start and end dates. Defaults to the configured user, but can retrieve worklogs for other users.

```
Parameters:
- startDate: String (YYYY-MM-DD)
- endDate: String (YYYY-MM-DD)
- userName: String (optional, user's display name or email)
- accountId: String (optional, Jira account ID - takes precedence over userName)
```

### createWorklog

Creates a new worklog for a specific Jira issue.
Creates a new worklog for a specific Jira issue. Defaults to the configured user, but can create worklogs for other users.

```
Parameters:
Expand All @@ -214,11 +238,13 @@ Parameters:
- date: String (YYYY-MM-DD)
- description: String (optional)
- startTime: String (HH:MM format, optional)
- userName: String (optional, user's display name or email)
- accountId: String (optional, Jira account ID - takes precedence over userName)
```

### bulkCreateWorklogs

Creates multiple worklogs in a single operation.
Creates multiple worklogs in a single operation. Defaults to the configured user, but can create worklogs for other users.

```
Parameters:
Expand All @@ -229,6 +255,8 @@ Parameters:
description: String (optional)
startTime: String (HH:MM format, optional)
}
- userName: String (optional, user's display name or email)
- accountId: String (optional, Jira account ID - takes precedence over userName)
```

### editWorklog
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 57 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import config from './config.js';
import * as tools from './tools.js';
import {
retrieveWorklogsSchema,
createWorklogSchema,
bulkCreateWorklogsSchema,
editWorklogSchema,
deleteWorklogSchema,
searchUserSchema,
retrieveWorklogsForUserSchema,
createWorklogForUserSchema,
bulkCreateWorklogsForUserSchema,
} from './types.js';

// Create MCP server instance
Expand All @@ -23,13 +24,41 @@ const server = new McpServer({
version: config.server.version,
});

// Tool: retrieveWorklogs - fetch worklogs between two dates
// Tool: searchUser - search for a Jira user by name or email
server.tool('searchUser', searchUserSchema.shape, async ({ query }) => {
try {
const result = await tools.searchUser(query);
return {
content: result.content,
};
} catch (error) {
console.error(
`[ERROR] searchUser failed: ${error instanceof Error ? error.message : String(error)}`,
);
return {
content: [
{
type: 'text',
text: `Error searching for user: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});

// Tool: retrieveWorklogs - fetch worklogs between two dates (for current user or specified user)
server.tool(
'retrieveWorklogs',
retrieveWorklogsSchema.shape,
async ({ startDate, endDate }) => {
retrieveWorklogsForUserSchema.shape,
async ({ startDate, endDate, userName, accountId }) => {
try {
const result = await tools.retrieveWorklogs(startDate, endDate);
const result = await tools.retrieveWorklogs(
startDate,
endDate,
userName,
accountId,
);
return {
content: result.content,
};
Expand All @@ -50,18 +79,28 @@ server.tool(
},
);

// Tool: createWorklog - create a single worklog entry
// Tool: createWorklog - create a single worklog entry (for current user or specified user)
server.tool(
'createWorklog',
createWorklogSchema.shape,
async ({ issueKey, timeSpentHours, date, description, startTime }) => {
createWorklogForUserSchema.shape,
async ({
issueKey,
timeSpentHours,
date,
description,
startTime,
userName,
accountId,
}) => {
try {
const result = await tools.createWorklog(
issueKey,
timeSpentHours,
date,
description,
startTime,
userName,
accountId,
);
return {
content: result.content,
Expand All @@ -83,13 +122,17 @@ server.tool(
},
);

// Tool: bulkCreateWorklogs - create multiple worklog entries at once
// Tool: bulkCreateWorklogs - create multiple worklog entries at once (for current user or specified user)
server.tool(
'bulkCreateWorklogs',
bulkCreateWorklogsSchema.shape,
async ({ worklogEntries }) => {
bulkCreateWorklogsForUserSchema.shape,
async ({ worklogEntries, userName, accountId }) => {
try {
const result = await tools.bulkCreateWorklogs(worklogEntries);
const result = await tools.bulkCreateWorklogs(
worklogEntries,
userName,
accountId,
);
return {
content: result.content,
};
Expand Down
29 changes: 29 additions & 0 deletions src/jira.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,35 @@ export async function getIssueKeyById(
}
}

/**
* Search for a Jira user by display name or email.
* Returns the first matching user's account ID.
*/
export async function searchUserByName(
query: string,
): Promise<{ accountId: string; displayName: string; emailAddress?: string }> {
try {
const response = await jiraApi.get<JiraUser[]>('/rest/api/3/user/search', {
params: { query },
});

const users = response.data;
if (!users || users.length === 0) {
throw new Error(`No user found matching: ${query}`);
}

// Return the first match
const user = users[0];
return {
accountId: user.accountId,
displayName: user.displayName || query,
emailAddress: user.emailAddress,
};
} catch (error) {
throw formatJiraError(error, `Failed to search for user: ${query}`);
}
}

/**
* Get Jira issue from issue ID or key
*/
Expand Down
Loading