internaltool-mcp 1.6.8 → 1.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +44 -9
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -18,7 +18,7 @@
18
18
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
19
19
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
20
20
  import { execSync } from 'child_process'
21
- import { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'fs'
21
+ import { mkdirSync, writeFileSync, unlinkSync, existsSync, readdirSync, statSync } from 'fs'
22
22
  import { join } from 'path'
23
23
  import { z } from 'zod'
24
24
  import { api, login, configure } from './api-client.js'
@@ -848,8 +848,9 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
848
848
  {
849
849
  taskId: z.string().describe("Task's MongoDB ObjectId"),
850
850
  confirmed: z.boolean().optional().default(false).describe('Set true after reading the plan to move the task to in_progress'),
851
+ repoPath: z.string().optional().describe('Absolute path to the local git repo (defaults to MCP process working directory). Used to write cursor rules file.'),
851
852
  },
852
- async ({ taskId, confirmed = false }) => {
853
+ async ({ taskId, confirmed = false, repoPath }) => {
853
854
  const taskRes = await api.get(`/api/tasks/${taskId}`)
854
855
  if (!taskRes?.success) return errorText('Task not found')
855
856
  const task = taskRes.data.task
@@ -960,6 +961,12 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
960
961
  } catch { /* might already be in_progress */ }
961
962
  }
962
963
 
964
+ // Write cursor rules file to local repo immediately on kickoff
965
+ let cursorRulesFile = null
966
+ if (hasCursorRules) {
967
+ cursorRulesFile = writeCursorRulesFile(task.key, task.cursorRules, repoPath)
968
+ }
969
+
963
970
  return text({
964
971
  started: {
965
972
  key: task.key,
@@ -973,6 +980,9 @@ Use this when a developer says "start task", "brief me on", or "what do I need t
973
980
  cursorRules: hasCursorRules
974
981
  ? { active: true, rules: task.cursorRules, instruction: '⚠️ CURSOR RULES ACTIVE — You MUST follow every rule in the "rules" field for the entire duration of this task.' }
975
982
  : { active: false },
983
+ cursorRulesFile: cursorRulesFile
984
+ ? { written: true, path: cursorRulesFile, note: 'Rules file written to your repo. Cursor enforces it automatically on every prompt.' }
985
+ : hasCursorRules ? { written: false, note: 'Could not write rules file — not inside a git repo.' } : null,
976
986
  recentCommits: recentCommits.slice(0, 5).map(c => ({
977
987
  sha: c.sha?.slice(0, 7),
978
988
  message: c.commit?.message?.split('\n')[0],
@@ -1229,10 +1239,33 @@ function runGit(args, cwd) {
1229
1239
  }).trim()
1230
1240
  }
1231
1241
 
1242
+ /**
1243
+ * Find the root of the git repo starting from `startPath`.
1244
+ * If `startPath` itself is not a git repo, scan one level of subdirectories.
1245
+ * Returns the repo root string or null.
1246
+ */
1247
+ function findRepoRoot(startPath) {
1248
+ const base = startPath || process.cwd()
1249
+ try {
1250
+ return runGit('rev-parse --show-toplevel', base)
1251
+ } catch { /* not a git repo — try subdirectories */ }
1252
+ try {
1253
+ const entries = readdirSync(base, { withFileTypes: true })
1254
+ for (const entry of entries) {
1255
+ if (!entry.isDirectory()) continue
1256
+ try {
1257
+ return runGit('rev-parse --show-toplevel', join(base, entry.name))
1258
+ } catch { /* not a git repo */ }
1259
+ }
1260
+ } catch { /* can't read dir */ }
1261
+ return null
1262
+ }
1263
+
1232
1264
  /** Write task-specific cursor rules to .cursor/rules/<taskKey>.mdc in the local repo root. */
1233
- function writeCursorRulesFile(taskKey, rulesMarkdown) {
1265
+ function writeCursorRulesFile(taskKey, rulesMarkdown, startPath) {
1234
1266
  try {
1235
- const repoRoot = runGit('rev-parse --show-toplevel', process.cwd())
1267
+ const repoRoot = findRepoRoot(startPath)
1268
+ if (!repoRoot) return null
1236
1269
  const rulesDir = join(repoRoot, '.cursor', 'rules')
1237
1270
  mkdirSync(rulesDir, { recursive: true })
1238
1271
  const filePath = join(rulesDir, `${taskKey.toLowerCase()}.mdc`)
@@ -1245,9 +1278,10 @@ function writeCursorRulesFile(taskKey, rulesMarkdown) {
1245
1278
  }
1246
1279
 
1247
1280
  /** Delete the task-specific cursor rules file when work is complete. */
1248
- function deleteCursorRulesFile(taskKey) {
1281
+ function deleteCursorRulesFile(taskKey, startPath) {
1249
1282
  try {
1250
- const repoRoot = runGit('rev-parse --show-toplevel', process.cwd())
1283
+ const repoRoot = findRepoRoot(startPath)
1284
+ if (!repoRoot) return null
1251
1285
  const filePath = join(repoRoot, '.cursor', 'rules', `${taskKey.toLowerCase()}.mdc`)
1252
1286
  if (existsSync(filePath)) {
1253
1287
  unlinkSync(filePath)
@@ -1681,7 +1715,7 @@ If you have uncommitted tracked changes, it will tell you exactly what to do bef
1681
1715
  const freshTaskForRules = await api.get(`/api/tasks/${taskId}`).catch(() => null)
1682
1716
  const cursorRulesContent = freshTaskForRules?.data?.task?.cursorRules
1683
1717
  if (cursorRulesContent?.trim()) {
1684
- cursorRulesFile = writeCursorRulesFile(freshTaskForRules.data.task.key, cursorRulesContent)
1718
+ cursorRulesFile = writeCursorRulesFile(freshTaskForRules.data.task.key, cursorRulesContent, cwd)
1685
1719
  }
1686
1720
 
1687
1721
  const checkoutSteps = [
@@ -1968,8 +2002,9 @@ Set confirmed=false first to preview the full PR content, then confirmed=true to
1968
2002
  additionalNotes: z.string().optional().describe('Extra context to add to the PR body'),
1969
2003
  draft: z.boolean().optional().default(false).describe('Open as a draft PR (not yet ready for review)'),
1970
2004
  confirmed: z.boolean().optional().default(false).describe('Set true to create the PR after reviewing the preview'),
2005
+ repoPath: z.string().optional().describe('Absolute path to the local git repo (defaults to MCP process working directory). Used to delete cursor rules file.'),
1971
2006
  },
1972
- async ({ taskId, projectId, headBranch, additionalNotes = '', draft = false, confirmed = false }) => {
2007
+ async ({ taskId, projectId, headBranch, additionalNotes = '', draft = false, confirmed = false, repoPath }) => {
1973
2008
  if (scopedProjectId && projectId !== scopedProjectId) {
1974
2009
  return errorText(`Access denied: session is scoped to project ${scopedProjectId}`)
1975
2010
  }
@@ -2017,7 +2052,7 @@ Set confirmed=false first to preview the full PR content, then confirmed=true to
2017
2052
  if (!res?.success) return errorText(res?.message || 'Could not create PR')
2018
2053
 
2019
2054
  // Delete the task-specific cursor rules file — coding is done
2020
- const deletedRulesFile = deleteCursorRulesFile(task.key)
2055
+ const deletedRulesFile = deleteCursorRulesFile(task.key, repoPath)
2021
2056
 
2022
2057
  return text({
2023
2058
  prNumber: res.data.prNumber,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internaltool-mcp",
3
- "version": "1.6.8",
3
+ "version": "1.6.10",
4
4
  "description": "MCP server for InternalTool — connect AI assistants (Claude Code, Cursor) to your project and task management platform",
5
5
  "type": "module",
6
6
  "main": "index.js",