-
Notifications
You must be signed in to change notification settings - Fork 81
@W-21277913 - New tool to create work item and change work item status #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
2d19cff
change work item status
ad-shreya 8c2c733
create new work item
ad-shreya 34b3f00
make subject mandatory
ad-shreya 059a160
add unit tests
ad-shreya 8722434
add license header and annotations
ad-shreya 885e299
Merge branch 'main' into ashreya/devops-mcp-wi
ad-shreya eaed04c
fix test failure
ad-shreya 01825c3
Merge branch 'main' into ashreya/devops-mcp-wi
ad-shreya e94124f
make optional
ad-shreya 4c74b34
Merge branch 'main' into ashreya/devops-mcp-wi
iowillhoit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import axios from "axios"; | ||
| import { getConnection } from "./shared/auth.js"; | ||
|
|
||
| const API_VERSION = "v65.0"; | ||
|
|
||
| export interface CreateWorkItemParams { | ||
| usernameOrAlias: string; | ||
| projectId: string; | ||
| subject: string; | ||
| description: string; | ||
| } | ||
|
|
||
| export interface CreateWorkItemResult { | ||
| success: boolean; | ||
| workItemId?: string; | ||
| workItemName?: string; | ||
| subject?: string; | ||
| error?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new DevOps Center Work Item in the specified project. | ||
| * API: POST /services/data/v65.0/connect/devops/projects/<ProjectID>/workitem | ||
| * Body: { subject: string, description: string } | ||
| */ | ||
| export async function createWorkItem(params: CreateWorkItemParams): Promise<CreateWorkItemResult> { | ||
| const { usernameOrAlias, projectId, subject, description } = params; | ||
|
|
||
| const connection = await getConnection(usernameOrAlias); | ||
| const accessToken = connection.accessToken; | ||
| const instanceUrl = connection.instanceUrl; | ||
| if (!accessToken || !instanceUrl) { | ||
| return { | ||
| success: false, | ||
| error: "Missing access token or instance URL.", | ||
| }; | ||
| } | ||
|
|
||
| const url = `${instanceUrl}/services/data/${API_VERSION}/connect/devops/projects/${projectId}/workitem`; | ||
| const headers = { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| "Content-Type": "application/json", | ||
| }; | ||
| const body = { subject, description }; | ||
|
|
||
| try { | ||
| const response = await axios.post(url, body, { headers }); | ||
| const data = response.data ?? {}; | ||
| return { | ||
| success: true, | ||
| workItemId: data.id ?? data.Id, | ||
| workItemName: data.name ?? data.Name, | ||
| subject: data.subject ?? data.Subject ?? subject, | ||
| }; | ||
| } catch (error: any) { | ||
| const data = error.response?.data; | ||
| const message = | ||
| (typeof data === "object" && (data?.message ?? data?.error ?? data?.errorDescription)) ?? | ||
| error.message ?? | ||
| "Unknown error"; | ||
| const details = Array.isArray(data?.body) ? data.body.join("; ") : undefined; | ||
| return { | ||
| success: false, | ||
| error: details ? `${message}: ${details}` : String(message), | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
packages/mcp-provider-devops/src/tools/sfDevopsCreateWorkItem.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| /* | ||
| * Copyright 2026, Salesforce, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| import { z } from "zod"; | ||
| import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; | ||
| import { McpTool, McpToolConfig, ReleaseState, Toolset, Services } from "@salesforce/mcp-provider-api"; | ||
| import { createWorkItem } from "../createWorkItem.js"; | ||
| import { TelemetryEventNames } from "../constants.js"; | ||
| import { usernameOrAliasParam } from "../shared/params.js"; | ||
|
|
||
| const inputSchema = z.object({ | ||
| usernameOrAlias: usernameOrAliasParam, | ||
| projectId: z.string().min(1).describe("DevOps Center Project ID selected from list_devops_center_projects for the same org."), | ||
| subject: z.string().min(1).describe("Work item subject."), | ||
| description: z.string().optional().describe("Work item description."), | ||
| }); | ||
| type InputArgs = z.infer<typeof inputSchema>; | ||
| type InputArgsShape = typeof inputSchema.shape; | ||
| type OutputArgsShape = z.ZodRawShape; | ||
|
|
||
| export class SfDevopsCreateWorkItem extends McpTool<InputArgsShape, OutputArgsShape> { | ||
| private readonly services: Services; | ||
|
|
||
| constructor(services: Services) { | ||
| super(); | ||
| this.services = services; | ||
| } | ||
|
|
||
| public getReleaseState(): ReleaseState { | ||
| return ReleaseState.NON_GA; | ||
| } | ||
|
|
||
| public getToolsets(): Toolset[] { | ||
| return [Toolset.DEVOPS]; | ||
| } | ||
|
|
||
| public getName(): string { | ||
| return "create_devops_center_work_item"; | ||
| } | ||
|
|
||
| public getConfig(): McpToolConfig<InputArgsShape, OutputArgsShape> { | ||
| return { | ||
| title: "Create Work Item", | ||
| description: `Creates a new DevOps Center Work Item in the specified project. | ||
|
|
||
| **Usage notes:** | ||
| - This tool must be used for the DevOps Center org only. If the org is not provided, use 'list_all_orgs' to select the DevOps Center org. | ||
| - A DevOps Center project must be selected first from the same org. If the projectId is not known, call 'list_devops_center_projects' for that org and ask the user to select a project. Use that project's Id here. | ||
| - Ensure the org used to select the project is the same org passed to this tool. | ||
| - **(Mandatory)** Always ask the user to give the work item subject. Don't proceed until the user has provided the subject. **(Mandatory)** | ||
|
|
||
| **API:** POST /services/data/v65.0/connect/devops/projects/<ProjectID>/workitem | ||
| **Body:** { "subject": string, "description": string } | ||
|
|
||
| **Input parameters:** | ||
| - usernameOrAlias: DevOps Center org username or alias. If missing, use 'list_all_orgs' and ask user to select the DevOps Center org. | ||
| - projectId: DevOps Center Project ID from list_devops_center_projects for the same org. | ||
| - subject: Work item subject. | ||
| - description: Work item description (optional). | ||
|
|
||
| **Output:** | ||
| - success: Whether the create succeeded. | ||
| - workItemId, workItemName, subject: Created work item details on success. | ||
| - error: Error message if the create failed.`, | ||
| inputSchema: inputSchema.shape, | ||
| outputSchema: undefined, | ||
| annotations: { | ||
| readOnlyHint: false, // Creates a work item (modifies state) | ||
| destructiveHint: false, // Does not delete anything | ||
| openWorldHint: true, // Calls Salesforce DevOps Center API | ||
| }, | ||
| }; | ||
ad-shreya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public async exec(input: InputArgs): Promise<CallToolResult> { | ||
| const startTime = Date.now(); | ||
|
|
||
| try { | ||
| const result = await createWorkItem({ | ||
| usernameOrAlias: input.usernameOrAlias, | ||
| projectId: input.projectId, | ||
| subject: input.subject, | ||
| description: input.description ?? "", | ||
| }); | ||
|
|
||
| const executionTime = Date.now() - startTime; | ||
|
|
||
| this.services.getTelemetryService().sendEvent(TelemetryEventNames.CREATE_WORK_ITEM, { | ||
| success: result.success, | ||
| projectId: input.projectId, | ||
| executionTimeMs: executionTime, | ||
| ...(result.error && { error: result.error }), | ||
| }); | ||
|
|
||
| if (!result.success) { | ||
| return { | ||
| content: [{ type: "text", text: JSON.stringify(result, null, 2) }], | ||
| isError: true, | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| content: [{ type: "text", text: JSON.stringify(result, null, 2) }], | ||
| }; | ||
| } catch (e: any) { | ||
| const executionTime = Date.now() - startTime; | ||
|
|
||
| this.services.getTelemetryService().sendEvent(TelemetryEventNames.CREATE_WORK_ITEM, { | ||
| success: false, | ||
| error: e?.message || "Unknown error", | ||
| projectId: input.projectId, | ||
| executionTimeMs: executionTime, | ||
| }); | ||
|
|
||
| return { | ||
| content: [{ type: "text", text: `Error creating work item: ${e?.message || e}` }], | ||
| isError: true, | ||
| }; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.