You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

cursor-azure-devops-mcp

Package Overview
Dependencies
Maintainers
0
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cursor-azure-devops-mcp - npm Package Compare versions

Comparing version

to
1.0.2

83

build/azure-devops-service.js

@@ -213,4 +213,7 @@ import * as azdev from 'azure-devops-node-api';

projectName);
// Get detailed content for each change (with size limits for safety)
const MAX_FILE_SIZE = 100000; // Limit file size to 100KB for performance
// File size and content handling constants
const MAX_INLINE_FILE_SIZE = 500000; // Increased to 500KB for inline content
const MAX_CHUNK_SIZE = 100000; // 100KB chunks for larger files
const PREVIEW_SIZE = 10000; // 10KB preview for very large files
// Get detailed content for each change
const enhancedChanges = await Promise.all((changes.changeEntries || []).map(async (change) => {

@@ -220,2 +223,6 @@ const filePath = change.item?.path || '';

let modifiedContent = null;
let originalContentSize = 0;
let modifiedContentSize = 0;
let originalContentPreview = null;
let modifiedContentPreview = null;
// Skip folders or binary files

@@ -229,9 +236,18 @@ const isBinary = this.isBinaryFile(filePath);

try {
const originalItemContent = await this.gitClient.getItemContent(repositoryId, filePath, projectName, change.originalObjectId, undefined, true, true);
// Check if the content is too large
if (originalItemContent && originalItemContent.length < MAX_FILE_SIZE) {
// First get the item metadata to check file size
const originalItem = await this.gitClient.getItem(repositoryId, filePath, projectName, change.originalObjectId);
originalContentSize = originalItem?.contentMetadata?.contentLength || 0;
// For files within the inline limit, get full content
if (originalContentSize <= MAX_INLINE_FILE_SIZE) {
const originalItemContent = await this.gitClient.getItemContent(repositoryId, filePath, projectName, change.originalObjectId, undefined, true, true);
originalContent = originalItemContent.toString('utf8');
}
// For large files, get a preview
else {
originalContent = '(File too large to display)';
// Get just the beginning of the file for preview
const previewContent = await this.gitClient.getItemText(repositoryId, filePath, projectName, change.originalObjectId, 0, // Start at beginning
PREVIEW_SIZE // Get preview bytes
);
originalContentPreview = previewContent;
originalContent = `(File too large to display inline - ${Math.round(originalContentSize / 1024)}KB. Preview shown.)`;
}

@@ -247,9 +263,18 @@ }

try {
const modifiedItemContent = await this.gitClient.getItemContent(repositoryId, filePath, projectName, change.item.objectId, undefined, true, true);
// Check if the content is too large
if (modifiedItemContent && modifiedItemContent.length < MAX_FILE_SIZE) {
// First get the item metadata to check file size
const modifiedItem = await this.gitClient.getItem(repositoryId, filePath, projectName, change.item.objectId);
modifiedContentSize = modifiedItem?.contentMetadata?.contentLength || 0;
// For files within the inline limit, get full content
if (modifiedContentSize <= MAX_INLINE_FILE_SIZE) {
const modifiedItemContent = await this.gitClient.getItemContent(repositoryId, filePath, projectName, change.item.objectId, undefined, true, true);
modifiedContent = modifiedItemContent.toString('utf8');
}
// For large files, get a preview
else {
modifiedContent = '(File too large to display)';
// Get just the beginning of the file for preview
const previewContent = await this.gitClient.getItemText(repositoryId, filePath, projectName, change.item.objectId, 0, // Start at beginning
PREVIEW_SIZE // Get preview bytes
);
modifiedContentPreview = previewContent;
modifiedContent = `(File too large to display inline - ${Math.round(modifiedContentSize / 1024)}KB. Preview shown.)`;
}

@@ -272,2 +297,8 @@ }

modifiedContent,
originalContentSize,
modifiedContentSize,
originalContentPreview,
modifiedContentPreview,
isBinary,
isFolder,
};

@@ -282,2 +313,34 @@ return enhancedChange;

/**
* Get content for a specific file in a pull request by chunks
* This allows retrieving parts of large files that can't be displayed inline
*/
async getPullRequestFileContent(repositoryId, pullRequestId, filePath, objectId, startPosition, length, project) {
await this.initialize();
if (!this.gitClient) {
throw new Error('Git client not initialized');
}
// Use the provided project or fall back to the default project
const projectName = project || this.defaultProject;
if (!projectName) {
throw new Error('Project name is required');
}
try {
// Get metadata about the file to know its full size
const item = await this.gitClient.getItem(repositoryId, filePath, projectName, objectId);
const fileSize = item?.contentMetadata?.contentLength || 0;
// Get the specified chunk of content
const content = await this.gitClient.getItemText(repositoryId, filePath, projectName, objectId, startPosition, length);
return {
content,
size: fileSize,
position: startPosition,
length: content.length,
};
}
catch (error) {
console.error(`Error getting file content for ${filePath}:`, error);
throw new Error(`Failed to retrieve content for file: ${filePath}`);
}
}
/**
* Helper function to determine if a file is likely binary based on extension

@@ -284,0 +347,0 @@ */

@@ -216,2 +216,22 @@ #!/usr/bin/env node

});
// New tool for getting content of large files in pull requests by chunks
server.tool('azure_devops_pull_request_file_content', 'Get content of a specific file in a pull request by chunks (for large files)', {
repositoryId: z.string().describe('Repository ID'),
pullRequestId: z.number().describe('Pull request ID'),
filePath: z.string().describe('File path'),
objectId: z.string().describe('Object ID of the file version'),
startPosition: z.number().describe('Starting position in the file (bytes)'),
length: z.number().describe('Length to read (bytes)'),
project: z.string().describe('Project name'),
}, async ({ repositoryId, pullRequestId, filePath, objectId, startPosition, length, project }) => {
const result = await azureDevOpsService.getPullRequestFileContent(repositoryId, pullRequestId, filePath, objectId, startPosition, length, project);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
});
// New tool for creating pull request comments

@@ -276,2 +296,3 @@ server.tool('azure_devops_create_pr_comment', 'Create a comment on a pull request', {

console.error('- azure_devops_pull_request_changes: Get detailed code changes for a pull request');
console.error('- azure_devops_pull_request_file_content: Get content of a specific file in chunks (for large files)');
console.error('- azure_devops_create_pr_comment: Create a comment on a pull request');

@@ -278,0 +299,0 @@ }

@@ -161,3 +161,4 @@ #!/usr/bin/env node

project: z.string().describe('Project name'),
}, async ({ repositoryId, pullRequestId, project }) => {
}, async ({ repositoryId, pullRequestId, project }, { signal }) => {
signal.throwIfAborted();
const result = await azureDevOpsService.getPullRequestChanges(repositoryId, pullRequestId, project);

@@ -173,2 +174,23 @@ return {

});
// New tool for getting content of large files in pull requests by chunks
server.tool('azure_devops_pull_request_file_content', 'Get content of a specific file in a pull request by chunks (for large files)', {
repositoryId: z.string().describe('Repository ID'),
pullRequestId: z.number().describe('Pull request ID'),
filePath: z.string().describe('File path'),
objectId: z.string().describe('Object ID of the file version'),
startPosition: z.number().describe('Starting position in the file (bytes)'),
length: z.number().describe('Length to read (bytes)'),
project: z.string().describe('Project name'),
}, async ({ repositoryId, pullRequestId, filePath, objectId, startPosition, length, project }, { signal }) => {
signal.throwIfAborted();
const result = await azureDevOpsService.getPullRequestFileContent(repositoryId, pullRequestId, filePath, objectId, startPosition, length, project);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
});
// New tool for creating pull request comments

@@ -175,0 +197,0 @@ server.tool('azure_devops_create_pr_comment', 'Create a comment on a pull request', {

2

package.json
{
"name": "cursor-azure-devops-mcp",
"version": "1.0.1",
"version": "1.0.2",
"description": "MCP Server for Cursor IDE-Azure DevOps Integration",

@@ -5,0 +5,0 @@ "main": "build/index.js",

@@ -151,4 +151,2 @@ # Cursor Azure DevOps MCP Server

![Command Mode Setup](https://raw.githubusercontent.com/yourusername/cursor-azure-devops-mcp/main/docs/images/command-mode-setup.png)
#### Option 2: SSE Mode (Alternative)

@@ -181,4 +179,2 @@

![SSE Mode Setup](https://raw.githubusercontent.com/yourusername/cursor-azure-devops-mcp/main/docs/images/sse-mode-setup.png)
### Windows Users

@@ -225,2 +221,3 @@

| `azure_devops_pull_request_changes` | Get detailed PR code changes | `repositoryId` (string), `pullRequestId` (number), `project` (string) |
| `azure_devops_pull_request_file_content` | Get content of large files in chunks | `repositoryId` (string), `pullRequestId` (number), `filePath` (string), `objectId` (string), `startPosition` (number), `length` (number), `project` (string) |
| `azure_devops_create_pr_comment` | Create a comment on a pull request | `repositoryId` (string), `pullRequestId` (number), `project` (string), `content` (string), and other optional parameters |

@@ -311,189 +308,2 @@

npm run test-connection
```
## Publishing
To publish to npm:
```bash
npm run build
npm publish
```
## Continuous Integration and Deployment
This project uses GitHub Actions for automated builds, testing, and publishing.
### Status Badges
The status badges at the top of the README provide real-time information about the project:
- **CI**: Shows the status of the continuous integration workflow (build and tests)
- **Publish**: Indicates the status of the npm publishing workflow
- **Version Bump**: Indicates the status of the version bump workflow
- **npm version**: Shows the latest published version on npm
- **License**: Indicates the project license type
### CI Workflow
The CI workflow runs on every push to the main and develop branches, as well as on pull requests:
- Builds the project with Node.js 16.x, 18.x, and 20.x
- Runs linting and formatting checks
- Performs security checks to prevent sensitive data exposure
- Ensures the build completes successfully
You can see the workflow details in `.github/workflows/ci.yml`.
### CD Workflow (Publishing)
The publishing workflow runs when:
- A change is pushed to the main branch that modifies `package.json` or source code
- A new GitHub release is created
- Manually triggered via GitHub Actions interface
It performs the following steps:
1. Runs all checks and builds the package
2. Extracts the package version from package.json
3. Checks if that version already exists on npm
4. If the version exists, automatically bumps the patch version
5. Creates a GitHub release for the new version
6. Publishes the package to npm
To use this workflow, you need to:
1. Add an `NPM_TOKEN` secret to your GitHub repository settings
2. Either:
- Push changes to the main branch (auto-versioning will handle the rest)
- Manually trigger the workflow from the Actions tab
- Create a GitHub release
The workflow supports automatic version bumping when a version conflict is detected, making continuous delivery seamless.
You can see the workflow details in `.github/workflows/publish.yml`.
## Troubleshooting
### Summary of Common Issues and Solutions
1. **Best Practices for Connection**:
- Use Command mode when possible (more reliable)
- If using SSE mode, set the port directly in the command line with `PORT=9836 npm run sse-server`
- Always verify the server is running before trying to connect from Cursor
2. **Port Configuration**:
- The most reliable way to change the port is using the environment variable directly: `PORT=9836 npm run sse-server`
- Make sure to use the same port in Cursor when adding the MCP server: `http://localhost:9836/sse`
3. **Session Management Issues**:
- If you see "Session not found" errors in the console, try restarting both the server and Cursor
- Clear Cursor's application data (Help > Clear Application Data) if problems persist
4. **Connection Errors**:
- For "Failed to create client" errors, make sure the server is running and accessible
- Check that no firewalls or security software are blocking the connection
- Try using a different port if the default port (3000) is in use
### MCP Server Not Connecting
- Make sure your Azure DevOps credentials in the `.env` file are correct
- Check that your personal access token has the necessary permissions:
- Code (read)
- Pull Request Threads (read)
- Work Items (read)
- If using SSE mode, ensure the server is running before adding the MCP server in Cursor
### Command Not Found
- If running with npx, make sure you have Node.js installed
- If using the global installation, try reinstalling: `npm install -g cursor-azure-devops-mcp`
### SSE Connection Issues
If you encounter JSON validation errors when connecting via SSE mode:
1. **Use Command Mode Instead**: The command mode is more reliable and recommended for most users.
2. **Check Cursor Version**: Ensure you're using Cursor version 0.46.9 or newer, as older versions had known issues with SSE connections.
3. **Run with Debug Logs**: Start the SSE server with debug logs:
```bash
DEBUG=* npm run sse-server
```
4. **Port Conflicts**: Make sure no other service is using port 3000. You can change the port by setting the `PORT` environment variable:
```bash
PORT=3001 npm run sse-server
```
Then use `http://localhost:3001/sse` as the SSE endpoint URL.
5. **Network Issues**: Make sure your firewall isn't blocking the connection.
6. **Verify Server is Running**: Make sure you can access the server's home page at `http://localhost:3000`
### "Cannot set headers after they are sent to the client" Error
If you encounter this error when running the SSE server:
1. **Check for duplicate header settings**: This error occurs when the application tries to set HTTP headers after they've already been sent. In the SSE implementation, make sure you're not manually setting headers that the `SSEServerTransport` already sets.
2. **Avoid manual header manipulation**: The `SSEServerTransport` class from the MCP SDK handles the necessary headers for SSE connections. Don't set these headers manually:
```javascript
// Don't set these manually in your route handlers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache, no-transform');
res.setHeader('Connection', 'keep-alive');
```
3. **Check response handling**: Ensure you're not sending multiple responses to the same request. Each HTTP request should have exactly one response.
### Port Configuration Issues
If you're changing the port in your `.env` file but the server still runs on port 3000, try one of these solutions:
1. **Set the PORT directly in the command line**:
```bash
PORT=9836 npm run sse-server
```
This is the most reliable way to change the port. The server will output the actual port it's using:
```
Server running on port 9836
SSE endpoint: http://localhost:9836/sse
Message endpoint: http://localhost:9836/message
```
2. **Verify your `.env` file is being loaded**:
Make sure your `.env` file is in the root directory of the project and has the correct format:
```
PORT=9836
HOST=localhost
AZURE_DEVOPS_ORG_URL=https://dev.azure.com/your-organization
AZURE_DEVOPS_TOKEN=your-personal-access-token
AZURE_DEVOPS_PROJECT=YourProject
LOG_LEVEL=info
```
3. **Use Command mode instead**:
The Command mode is more reliable and doesn't require managing a separate HTTP server.
### Connecting to Cursor with Custom Port
If you're running the SSE server on a custom port (e.g., 9836), make sure to use the correct port when adding the MCP server in Cursor:
1. Start the server with your custom port:
```bash
PORT=9836 npm run sse-server
```
2. In Cursor IDE, when adding the MCP server, use the correct port in the SSE endpoint URL:
```
http://localhost:9836/sse
```
## License
MIT
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
```