@pathmode/mcp-server 1.20.0 → 1.20.1

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.
package/dist/index.js CHANGED
@@ -39615,6 +39615,9 @@ async function exchangeCode(code, origin, existing) {
39615
39615
  }
39616
39616
  /** Add cloud identity to the existing frontmatter without reformatting the user's spec body. */
39617
39617
  function patchAdoptedIntentFrontmatter(filePath, values) {
39618
+ if (fs_1.default.lstatSync(filePath).isSymbolicLink()) {
39619
+ throw new Error('Refusing to update a symbolic-link intent.md. Use a regular file inside the repository.');
39620
+ }
39618
39621
  const raw = fs_1.default.readFileSync(filePath, 'utf8');
39619
39622
  const newline = raw.includes('\r\n') ? '\r\n' : '\n';
39620
39623
  if (!raw.startsWith(`---${newline}`)) {
@@ -39646,6 +39649,9 @@ async function runAdopt(argv = process.argv) {
39646
39649
  if (!code)
39647
39650
  throw new Error('Usage: npx @pathmode/mcp-server@latest adopt pm_adopt_…');
39648
39651
  const filePath = path_1.default.join(process.cwd(), 'intent.md');
39652
+ if (fs_1.default.existsSync(filePath) && fs_1.default.lstatSync(filePath).isSymbolicLink()) {
39653
+ throw new Error('Refusing to adopt a symbolic-link intent.md. Use a regular file inside the repository.');
39654
+ }
39649
39655
  const intent = (0, local_reader_1.readIntentFile)(filePath);
39650
39656
  const meta = (0, local_reader_1.readIntentMeta)(filePath);
39651
39657
  if (!intent || !meta?.id) {
@@ -39654,7 +39660,7 @@ async function runAdopt(argv = process.argv) {
39654
39660
  console.log('');
39655
39661
  console.log('Pathmode repo adoption');
39656
39662
  console.log('──────────────────────');
39657
- console.log(` Found: ${intent.title}`);
39663
+ console.log(` Read intent.md — ${intent.title}`);
39658
39664
  process.stdout.write(' Connecting…');
39659
39665
  const origin = apiOrigin(argv);
39660
39666
  const current = (0, api_client_1.loadConfig)();
@@ -39674,6 +39680,8 @@ async function runAdopt(argv = process.argv) {
39674
39680
  throw error;
39675
39681
  }
39676
39682
  }
39683
+ console.log(' ✓');
39684
+ console.log(` ✓ Destination confirmed — ${exchange.workspaceName} / ${exchange.productName}`);
39677
39685
  const apiKey = exchange.apiKey || current?.apiKey;
39678
39686
  if (!apiKey)
39679
39687
  throw new Error('No repository credential is available after adoption exchange.');
@@ -39686,7 +39694,10 @@ async function runAdopt(argv = process.argv) {
39686
39694
  // credential remains recoverable from this repo without minting a duplicate key.
39687
39695
  (0, project_config_1.saveProjectConnection)(config);
39688
39696
  (0, project_config_1.ensureProjectMcpConfig)();
39697
+ console.log(' ✓ Credential stored outside the repository');
39698
+ console.log(' ✓ Pathmode project entries added without the credential');
39689
39699
  const client = new api_client_1.PathmodeClient(config);
39700
+ console.log(' • Creating and linking the hosted intent…');
39690
39701
  const pushed = await (0, push_spec_1.pushSpec)({
39691
39702
  client,
39692
39703
  id: meta.id,
@@ -39717,11 +39728,17 @@ async function runAdopt(argv = process.argv) {
39717
39728
  if (!pushed.ok)
39718
39729
  throw new Error(pushed.error);
39719
39730
  await client.completeRepoOnboarding(exchange.sessionId, pushed.canonicalId);
39720
- console.log(' ');
39721
- console.log(` Adopted into ${exchange.workspaceName} / ${exchange.productName}`);
39731
+ console.log(' Hosted intent linked');
39722
39732
  console.log(` ${pushed.sourceUrl}`);
39723
39733
  console.log('');
39724
- console.log('Restart your coding tool once so it reloads the connected Pathmode server.');
39734
+ console.log('What changed');
39735
+ console.log(' • intent.md remains authoritative; only its cloud identity frontmatter changed');
39736
+ console.log(' • Project connection files contain no Pathmode credential');
39737
+ console.log(' • The hosted record is ready for human review and authorization');
39738
+ console.log('');
39739
+ console.log('Next');
39740
+ console.log(' Keep the browser page open; it finishes automatically.');
39741
+ console.log(' Restart your coding tool once so it reloads .mcp.json and discovers Pathmode.');
39725
39742
  console.log('');
39726
39743
  }
39727
39744
 
@@ -41490,9 +41507,11 @@ function extractVerification(sections) {
41490
41507
  const result = {};
41491
41508
  let currentKind = null;
41492
41509
  let currentLegacy = null;
41510
+ let sawLabel = false;
41493
41511
  for (const line of lines) {
41494
41512
  const label = parseVerificationLabel(line);
41495
41513
  if (label) {
41514
+ sawLabel = true;
41496
41515
  const kind = VERIFICATION_LABEL_TO_KIND[label.toLowerCase()];
41497
41516
  if (kind) {
41498
41517
  currentKind = kind;
@@ -41531,6 +41550,11 @@ function extractVerification(sections) {
41531
41550
  else if (currentLegacy) {
41532
41551
  result[currentLegacy].push(text);
41533
41552
  }
41553
+ else if (!sawLabel) {
41554
+ // IntentSpec labels are optional. Treat an ungrouped list as manual checks so a valid
41555
+ // flat `## Verification` section survives adoption instead of silently disappearing.
41556
+ (result.manualChecks ||= []).push(text);
41557
+ }
41534
41558
  }
41535
41559
  return result;
41536
41560
  }
@@ -42216,6 +42240,7 @@ exports.ensureProjectMcpConfig = ensureProjectMcpConfig;
42216
42240
  const fs_1 = __importDefault(__nccwpck_require__(9896));
42217
42241
  const os_1 = __importDefault(__nccwpck_require__(857));
42218
42242
  const path_1 = __importDefault(__nccwpck_require__(6928));
42243
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
42219
42244
  const homeWorkspaceFile = (workspaceId, homeDir = os_1.default.homedir()) => path_1.default.join(homeDir, '.pathmode', 'workspaces', `${workspaceId}.json`);
42220
42245
  function projectBindingFile(cwd = process.cwd()) {
42221
42246
  return path_1.default.join(cwd, '.pathmode', 'config.json');
@@ -42228,32 +42253,73 @@ function readJson(filePath) {
42228
42253
  return null;
42229
42254
  }
42230
42255
  }
42256
+ function normalizeApiUrl(raw) {
42257
+ if (typeof raw !== 'string' || !raw.trim())
42258
+ return null;
42259
+ try {
42260
+ const url = new URL(raw.trim());
42261
+ if (url.protocol !== 'https:' && url.protocol !== 'http:')
42262
+ return null;
42263
+ return url.origin;
42264
+ }
42265
+ catch {
42266
+ return null;
42267
+ }
42268
+ }
42269
+ /** Repository files are attacker-controlled when a project or branch is first opened. */
42270
+ function assertSafeProjectWrite(cwd, filePath) {
42271
+ const root = path_1.default.resolve(cwd);
42272
+ const target = path_1.default.resolve(filePath);
42273
+ const relative = path_1.default.relative(root, target);
42274
+ if (!relative || relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
42275
+ throw new Error(`Refusing to write outside the repository: ${filePath}`);
42276
+ }
42277
+ let current = root;
42278
+ for (const segment of relative.split(path_1.default.sep)) {
42279
+ current = path_1.default.join(current, segment);
42280
+ if (fs_1.default.existsSync(current) && fs_1.default.lstatSync(current).isSymbolicLink()) {
42281
+ throw new Error(`Refusing to write through a symbolic link: ${current}`);
42282
+ }
42283
+ }
42284
+ }
42285
+ function writeProjectFile(cwd, filePath, contents) {
42286
+ assertSafeProjectWrite(cwd, filePath);
42287
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
42288
+ // Re-check after creating a missing parent so a checked-in symlink is never followed.
42289
+ assertSafeProjectWrite(cwd, filePath);
42290
+ fs_1.default.writeFileSync(filePath, contents, 'utf8');
42291
+ }
42231
42292
  function loadProjectConnection(cwd = process.cwd(), homeDir = os_1.default.homedir()) {
42232
42293
  const binding = readJson(projectBindingFile(cwd));
42233
42294
  const workspaceId = typeof binding?.workspaceId === 'string' ? binding.workspaceId.trim() : '';
42234
- if (!workspaceId)
42295
+ if (!UUID_RE.test(workspaceId))
42235
42296
  return null;
42236
42297
  const secret = readJson(homeWorkspaceFile(workspaceId, homeDir));
42237
42298
  const apiKey = typeof secret?.apiKey === 'string' ? secret.apiKey.trim() : '';
42238
42299
  if (!apiKey)
42239
42300
  return null;
42240
- const apiUrl = typeof binding?.apiUrl === 'string' && binding.apiUrl.trim()
42241
- ? binding.apiUrl.trim()
42242
- : (typeof secret?.apiUrl === 'string' && secret.apiUrl.trim() ? secret.apiUrl.trim() : 'https://pathmode.io');
42301
+ // The repository chooses only which workspace to use. The protected home secret
42302
+ // chooses where that credential may be sent, so a malicious branch cannot redirect it.
42303
+ const apiUrl = normalizeApiUrl(secret?.apiUrl);
42304
+ if (!apiUrl)
42305
+ return null;
42243
42306
  return { apiKey, apiUrl, workspaceId };
42244
42307
  }
42245
42308
  function saveProjectConnection(config, cwd = process.cwd(), homeDir = os_1.default.homedir()) {
42309
+ if (!UUID_RE.test(config.workspaceId))
42310
+ throw new Error('Cannot save a project connection with an invalid workspace id.');
42311
+ const apiUrl = normalizeApiUrl(config.apiUrl);
42312
+ if (!apiUrl)
42313
+ throw new Error('Cannot save a project connection with an invalid API URL.');
42246
42314
  const bindingPath = projectBindingFile(cwd);
42247
- fs_1.default.mkdirSync(path_1.default.dirname(bindingPath), { recursive: true });
42248
- fs_1.default.writeFileSync(bindingPath, JSON.stringify({
42315
+ writeProjectFile(cwd, bindingPath, JSON.stringify({
42249
42316
  workspaceId: config.workspaceId,
42250
- apiUrl: config.apiUrl,
42251
- }, null, 2) + '\n', 'utf8');
42317
+ }, null, 2) + '\n');
42252
42318
  const secretPath = homeWorkspaceFile(config.workspaceId, homeDir);
42253
42319
  fs_1.default.mkdirSync(path_1.default.dirname(secretPath), { recursive: true });
42254
42320
  fs_1.default.writeFileSync(secretPath, JSON.stringify({
42255
42321
  apiKey: config.apiKey,
42256
- apiUrl: config.apiUrl,
42322
+ apiUrl,
42257
42323
  }, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
42258
42324
  try {
42259
42325
  fs_1.default.chmodSync(secretPath, 0o600);
@@ -42264,6 +42330,7 @@ function saveProjectConnection(config, cwd = process.cwd(), homeDir = os_1.defau
42264
42330
  * from the project binding at runtime and is never written into a committable file. */
42265
42331
  function ensureProjectMcpConfig(cwd = process.cwd()) {
42266
42332
  const configPath = path_1.default.join(cwd, '.mcp.json');
42333
+ assertSafeProjectWrite(cwd, configPath);
42267
42334
  const existing = fs_1.default.existsSync(configPath) ? readJson(configPath) : {};
42268
42335
  if (!existing)
42269
42336
  throw new Error(`${configPath} is not valid JSON; fix it before adopting.`);
@@ -42271,7 +42338,7 @@ function ensureProjectMcpConfig(cwd = process.cwd()) {
42271
42338
  ? existing.mcpServers
42272
42339
  : {};
42273
42340
  servers.pathmode = { command: 'npx', args: ['@pathmode/mcp-server'] };
42274
- fs_1.default.writeFileSync(configPath, JSON.stringify({ ...existing, mcpServers: servers }, null, 2) + '\n', 'utf8');
42341
+ writeProjectFile(cwd, configPath, JSON.stringify({ ...existing, mcpServers: servers }, null, 2) + '\n');
42275
42342
  }
42276
42343
 
42277
42344
 
@@ -71946,7 +72013,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"$schema":"http://json-schema.org/dra
71946
72013
  /***/ ((module) => {
71947
72014
 
71948
72015
  "use strict";
71949
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.20.0","publishConfig":{"access":"public"},"mcpName":"io.github.pathmodeio/mcp-server","description":"Deterministic intent preflight before your agent builds: six calibrated gates, keyless, no model call. Draft and sharpen specs in conversation, or connect a Pathmode workspace to sync intent and evidence across a team.","main":"dist/index.js","bin":{"pathmode-mcp":"dist/index.js"},"files":["dist/","manifest.json","icon.svg","README.md","skills/"],"scripts":{"build":"rm -rf dist && ncc build src/index.ts -o dist","dev":"ts-node src/index.ts","prepublishOnly":"npm run build"},"keywords":["pathmode","mcp","model-context-protocol","claude-code","claude-code-skills","agent-skills","cursor","windsurf","intent-engineering","intent-compiler","ai-agents","product-development","dependency-graph","strategic-planning"],"author":"Pathmode","license":"MIT","type":"commonjs","engines":{"node":">=18.0.0"},"homepage":"https://pathmode.io","dependencies":{"@modelcontextprotocol/sdk":"^1.12.1","gray-matter":"^4.0.3","zod":"^3.24.0"},"devDependencies":{"@types/node":"^25.1.0","@vercel/ncc":"^0.38.4","ts-node":"^10.9.2","typescript":"^5.9.3"}}');
72016
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.20.1","publishConfig":{"access":"public"},"mcpName":"io.github.pathmodeio/mcp-server","description":"Deterministic intent preflight before your agent builds: six calibrated gates, keyless, no model call. Draft and sharpen specs in conversation, or connect a Pathmode workspace to sync intent and evidence across a team.","main":"dist/index.js","bin":{"pathmode-mcp":"dist/index.js"},"files":["dist/","manifest.json","icon.svg","README.md","skills/"],"scripts":{"build":"rm -rf dist && ncc build src/index.ts -o dist","dev":"ts-node src/index.ts","prepublishOnly":"npm run build"},"keywords":["pathmode","mcp","model-context-protocol","claude-code","claude-code-skills","agent-skills","cursor","windsurf","intent-engineering","intent-compiler","ai-agents","product-development","dependency-graph","strategic-planning"],"author":"Pathmode","license":"MIT","type":"commonjs","engines":{"node":">=18.0.0"},"homepage":"https://pathmode.io","dependencies":{"@modelcontextprotocol/sdk":"^1.12.1","gray-matter":"^4.0.3","zod":"^3.24.0"},"devDependencies":{"@types/node":"^25.1.0","@vercel/ncc":"^0.38.4","ts-node":"^10.9.2","typescript":"^5.9.3"}}');
71950
72017
 
71951
72018
  /***/ })
71952
72019
 
@@ -1 +1 @@
1
- {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/adopt.ts"],"names":[],"mappings":"AAoBA,wBAAgB,cAAc,CAAC,IAAI,WAAe,GAAG,OAAO,CAE3D;AA8BD,gGAAgG;AAChG,wBAAgB,6BAA6B,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1J,IAAI,CA0BN;AAED,wBAAsB,QAAQ,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFjE"}
1
+ {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/adopt.ts"],"names":[],"mappings":"AAoBA,wBAAgB,cAAc,CAAC,IAAI,WAAe,GAAG,OAAO,CAE3D;AA8BD,gGAAgG;AAChG,wBAAgB,6BAA6B,CACzC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1J,IAAI,CA6BN;AAED,wBAAsB,QAAQ,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkGjE"}
@@ -1,7 +1,6 @@
1
1
  import type { PathmodeConfig } from './api-client';
2
2
  export interface ProjectBinding {
3
3
  workspaceId: string;
4
- apiUrl: string;
5
4
  }
6
5
  export declare function projectBindingFile(cwd?: string): string;
7
6
  export declare function loadProjectConnection(cwd?: string, homeDir?: string): PathmodeConfig | null;
@@ -1 +1 @@
1
- {"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/project-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,kBAAkB,CAAC,GAAG,SAAgB,GAAG,MAAM,CAE9D;AAUD,wBAAgB,qBAAqB,CAAC,GAAG,SAAgB,EAAE,OAAO,SAAe,GAAG,cAAc,GAAG,IAAI,CAaxG;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,SAAgB,EAAE,OAAO,SAAe,GAAG,IAAI,CAe/G;AAED;uFACuF;AACvF,wBAAgB,sBAAsB,CAAC,GAAG,SAAgB,GAAG,IAAI,CAShE"}
1
+ {"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/project-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC3B,WAAW,EAAE,MAAM,CAAC;CACvB;AAOD,wBAAgB,kBAAkB,CAAC,GAAG,SAAgB,GAAG,MAAM,CAE9D;AA+CD,wBAAgB,qBAAqB,CAAC,GAAG,SAAgB,EAAE,OAAO,SAAe,GAAG,cAAc,GAAG,IAAI,CAcxG;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,SAAgB,EAAE,OAAO,SAAe,GAAG,IAAI,CAiB/G;AAED;uFACuF;AACvF,wBAAgB,sBAAsB,CAAC,GAAG,SAAgB,GAAG,IAAI,CAUhE"}
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "pathmode",
4
4
  "display_name": "Pathmode",
5
- "version": "1.20.0",
5
+ "version": "1.20.1",
6
6
  "description": "Deterministic preflight for the intent you hand to a coding agent: six calibrated gates, the exact blockers named, no model call, no key needed.",
7
7
  "long_description": "Pathmode MCP Server runs a deterministic preflight before your coding agent builds: check_intent_readiness scores an intent spec against six calibrated gates (title, objective, outcomes, constraints, edge cases, verification) and names the exact blockers, with no model call and no account. Keyless local mode works out of the box; specs live in intent.md in your repo, plain markdown you own, and skills for drafting, pressure-testing, and handing off intent ride along. Connect a Pathmode workspace with an API key to sync intent and evidence across a team, analyze dependency graphs, and verify pull requests against the outcomes you agreed to.",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathmode/mcp-server",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },