mcp-perforce-server 2.0.1 → 2.1.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.
Files changed (45) hide show
  1. package/LICENSE +20 -20
  2. package/MCP_CONFIG_EXAMPLES.md +222 -222
  3. package/README.md +130 -515
  4. package/dist/p4/config.d.ts +2 -0
  5. package/dist/p4/config.d.ts.map +1 -1
  6. package/dist/p4/config.js +60 -31
  7. package/dist/p4/config.js.map +1 -1
  8. package/dist/p4/parse.d.ts +56 -28
  9. package/dist/p4/parse.d.ts.map +1 -1
  10. package/dist/p4/parse.js +462 -29
  11. package/dist/p4/parse.js.map +1 -1
  12. package/dist/p4/runner.d.ts +2 -0
  13. package/dist/p4/runner.d.ts.map +1 -1
  14. package/dist/p4/runner.js +266 -35
  15. package/dist/p4/runner.js.map +1 -1
  16. package/dist/p4/security.d.ts +2 -0
  17. package/dist/p4/security.d.ts.map +1 -1
  18. package/dist/p4/security.js +61 -29
  19. package/dist/p4/security.js.map +1 -1
  20. package/dist/performance.d.ts +47 -0
  21. package/dist/performance.d.ts.map +1 -0
  22. package/dist/performance.js +67 -0
  23. package/dist/performance.js.map +1 -0
  24. package/dist/server.d.ts +15 -0
  25. package/dist/server.d.ts.map +1 -1
  26. package/dist/server.js +418 -135
  27. package/dist/server.js.map +1 -1
  28. package/dist/tools/advanced.js +1 -1
  29. package/dist/tools/advanced.js.map +1 -1
  30. package/dist/tools/basic.d.ts +58 -0
  31. package/dist/tools/basic.d.ts.map +1 -1
  32. package/dist/tools/basic.js +492 -60
  33. package/dist/tools/basic.js.map +1 -1
  34. package/dist/tools/changelist.d.ts +1 -1
  35. package/dist/tools/changelist.d.ts.map +1 -1
  36. package/dist/tools/changelist.js +36 -6
  37. package/dist/tools/changelist.js.map +1 -1
  38. package/dist/tools/index.d.ts +1 -1
  39. package/dist/tools/index.d.ts.map +1 -1
  40. package/dist/tools/index.js +8 -1
  41. package/dist/tools/index.js.map +1 -1
  42. package/dist/tools/utils.d.ts.map +1 -1
  43. package/dist/tools/utils.js +24 -3
  44. package/dist/tools/utils.js.map +1 -1
  45. package/package.json +114 -114
package/dist/server.js CHANGED
@@ -54,8 +54,116 @@ const log = {
54
54
  info: (...args) => shouldLog('info') && console.error('[INFO]', ...args),
55
55
  debug: (...args) => shouldLog('debug') && console.error('[DEBUG]', ...args),
56
56
  };
57
+ const TOOL_HANDLERS = {
58
+ 'p4.info': tools.p4Info,
59
+ 'p4.status': tools.p4Status,
60
+ 'p4.add': tools.p4Add,
61
+ 'p4.edit': tools.p4Edit,
62
+ 'p4.delete': tools.p4Delete,
63
+ 'p4.revert': tools.p4Revert,
64
+ 'p4.sync': tools.p4Sync,
65
+ 'p4.opened': tools.p4Opened,
66
+ 'p4.diff': tools.p4Diff,
67
+ 'p4.diff2': tools.p4Diff2,
68
+ 'p4.changelist.create': tools.p4ChangelistCreate,
69
+ 'p4.changelist.update': tools.p4ChangelistUpdate,
70
+ 'p4.changelist.submit': tools.p4ChangelistSubmit,
71
+ 'p4.submit': tools.p4Submit,
72
+ 'p4.describe': tools.p4Describe,
73
+ 'p4.filelog': tools.p4Filelog,
74
+ 'p4.clients': tools.p4Clients,
75
+ 'p4.config.detect': tools.p4ConfigDetect,
76
+ 'p4.resolve': tools.p4Resolve,
77
+ 'p4.shelve': tools.p4Shelve,
78
+ 'p4.unshelve': tools.p4Unshelve,
79
+ 'p4.changes': tools.p4Changes,
80
+ 'p4.blame': tools.p4Blame,
81
+ 'p4.copy': tools.p4Copy,
82
+ 'p4.move': tools.p4Move,
83
+ 'p4.integrate': tools.p4Integrate,
84
+ 'p4.merge': tools.p4Merge,
85
+ 'p4.print': tools.p4Print,
86
+ 'p4.fstat': tools.p4Fstat,
87
+ 'p4.streams': tools.p4Streams,
88
+ 'p4.stream': tools.p4Stream,
89
+ 'p4.grep': tools.p4Grep,
90
+ 'p4.files': tools.p4Files,
91
+ 'p4.dirs': tools.p4Dirs,
92
+ 'p4.users': tools.p4Users,
93
+ 'p4.user': tools.p4User,
94
+ 'p4.client': tools.p4Client,
95
+ 'p4.jobs': tools.p4Jobs,
96
+ 'p4.job': tools.p4Job,
97
+ 'p4.fixes': tools.p4Fixes,
98
+ 'p4.labels': tools.p4Labels,
99
+ 'p4.label': tools.p4Label,
100
+ 'p4.sizes': tools.p4Sizes,
101
+ 'p4.have': tools.p4Have,
102
+ 'p4.where': tools.p4Where,
103
+ 'p4.audit': tools.p4Audit,
104
+ 'p4.compliance': tools.p4Compliance,
105
+ };
106
+ const WRITE_TOOLS = new Set([
107
+ 'p4.add',
108
+ 'p4.edit',
109
+ 'p4.delete',
110
+ 'p4.revert',
111
+ 'p4.sync',
112
+ 'p4.changelist.create',
113
+ 'p4.changelist.update',
114
+ 'p4.changelist.submit',
115
+ 'p4.submit',
116
+ 'p4.resolve',
117
+ 'p4.shelve',
118
+ 'p4.unshelve',
119
+ 'p4.copy',
120
+ 'p4.move',
121
+ 'p4.integrate',
122
+ 'p4.merge',
123
+ ]);
124
+ const CACHEABLE_TOOLS = new Set(Object.keys(TOOL_HANDLERS).filter((toolName) => !WRITE_TOOLS.has(toolName)));
125
+ function getPerformanceMode() {
126
+ const mode = (process.env.P4_PERFORMANCE_MODE || 'fast').toLowerCase();
127
+ if (mode === 'balanced' || mode === 'secure') {
128
+ return mode;
129
+ }
130
+ return 'fast';
131
+ }
132
+ function getDefaultResponseCacheTtlMs() {
133
+ switch (getPerformanceMode()) {
134
+ case 'secure':
135
+ return 1000;
136
+ case 'balanced':
137
+ return 3000;
138
+ case 'fast':
139
+ default:
140
+ return 5000;
141
+ }
142
+ }
143
+ function getDefaultResponseCacheMaxEntries() {
144
+ switch (getPerformanceMode()) {
145
+ case 'secure':
146
+ return 100;
147
+ case 'balanced':
148
+ return 250;
149
+ case 'fast':
150
+ default:
151
+ return 400;
152
+ }
153
+ }
154
+ function getEnvInt(name, fallback) {
155
+ const parsed = parseInt(process.env[name] || '', 10);
156
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
157
+ }
57
158
  class MCPPerforceServer {
58
159
  constructor() {
160
+ this.prettyJson = process.env.P4_PRETTY_JSON === 'true';
161
+ this.responseCacheEnabled = process.env.P4_RESPONSE_CACHE !== 'false';
162
+ this.responseCacheTtlMs = getEnvInt('P4_RESPONSE_CACHE_TTL_MS', getDefaultResponseCacheTtlMs());
163
+ this.responseCacheMaxEntries = getEnvInt('P4_RESPONSE_CACHE_MAX_ENTRIES', getDefaultResponseCacheMaxEntries());
164
+ this.responseCache = new Map();
165
+ this.inFlightReadRequests = new Map();
166
+ this.cacheEpoch = 0;
59
167
  this.server = new index_js_1.Server({
60
168
  name: 'mcp-perforce-server',
61
169
  version: '1.2.0',
@@ -88,6 +196,89 @@ class MCPPerforceServer {
88
196
  process.exit(0);
89
197
  });
90
198
  }
199
+ serializeResult(value) {
200
+ if (this.prettyJson) {
201
+ return JSON.stringify(value, null, 2);
202
+ }
203
+ return JSON.stringify(value);
204
+ }
205
+ toTextResponse(value) {
206
+ return {
207
+ content: [{ type: 'text', text: this.serializeResult(value) }],
208
+ };
209
+ }
210
+ getCachedResult(cacheKey) {
211
+ const entry = this.responseCache.get(cacheKey);
212
+ if (!entry) {
213
+ return undefined;
214
+ }
215
+ if (Date.now() > entry.expiresAt) {
216
+ this.responseCache.delete(cacheKey);
217
+ return undefined;
218
+ }
219
+ return entry.value;
220
+ }
221
+ setCachedResult(cacheKey, value) {
222
+ if (this.responseCacheMaxEntries <= 0 || this.responseCacheTtlMs <= 0) {
223
+ return;
224
+ }
225
+ if (this.responseCache.size >= this.responseCacheMaxEntries) {
226
+ const oldestKey = this.responseCache.keys().next().value;
227
+ if (oldestKey) {
228
+ this.responseCache.delete(oldestKey);
229
+ }
230
+ }
231
+ this.responseCache.set(cacheKey, {
232
+ value,
233
+ expiresAt: Date.now() + this.responseCacheTtlMs,
234
+ });
235
+ }
236
+ clearReadCache() {
237
+ this.cacheEpoch += 1;
238
+ if (this.responseCache.size > 0) {
239
+ this.responseCache.clear();
240
+ }
241
+ }
242
+ buildCacheKey(name, args) {
243
+ return `${name}:${JSON.stringify(args ?? {})}`;
244
+ }
245
+ async executeTool(name, args) {
246
+ const handler = TOOL_HANDLERS[name];
247
+ if (!handler) {
248
+ throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
249
+ }
250
+ return handler(this.context, args);
251
+ }
252
+ async executeToolWithCaching(name, args) {
253
+ if (!this.responseCacheEnabled || !CACHEABLE_TOOLS.has(name)) {
254
+ return this.executeTool(name, args);
255
+ }
256
+ const cacheKey = this.buildCacheKey(name, args);
257
+ const cachedResult = this.getCachedResult(cacheKey);
258
+ if (cachedResult !== undefined) {
259
+ return cachedResult;
260
+ }
261
+ const inFlight = this.inFlightReadRequests.get(cacheKey);
262
+ if (inFlight) {
263
+ return inFlight;
264
+ }
265
+ const pending = this.executeTool(name, args);
266
+ this.inFlightReadRequests.set(cacheKey, pending);
267
+ const startedEpoch = this.cacheEpoch;
268
+ try {
269
+ const result = await pending;
270
+ if (startedEpoch === this.cacheEpoch &&
271
+ result &&
272
+ typeof result === 'object' &&
273
+ result.ok === true) {
274
+ this.setCachedResult(cacheKey, result);
275
+ }
276
+ return result;
277
+ }
278
+ finally {
279
+ this.inFlightReadRequests.delete(cacheKey);
280
+ }
281
+ }
91
282
  setupToolHandlers() {
92
283
  this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
93
284
  log.debug('Listing available tools');
@@ -265,7 +456,7 @@ class MCPPerforceServer {
265
456
  },
266
457
  {
267
458
  name: 'p4.diff',
268
- description: 'Show differences for files',
459
+ description: 'Show differences for workspace files (opened or local vs depot)',
269
460
  inputSchema: {
270
461
  type: 'object',
271
462
  properties: {
@@ -286,6 +477,33 @@ class MCPPerforceServer {
286
477
  additionalProperties: false,
287
478
  },
288
479
  },
480
+ {
481
+ name: 'p4.diff2',
482
+ description: 'Compare two depot paths server-side (depot-to-depot diff, no client mapping required)',
483
+ inputSchema: {
484
+ type: 'object',
485
+ properties: {
486
+ sourcePath: {
487
+ type: 'string',
488
+ description: 'First depot filespec/path to compare (required)',
489
+ },
490
+ targetPath: {
491
+ type: 'string',
492
+ description: 'Second depot filespec/path to compare (required)',
493
+ },
494
+ summaryOnly: {
495
+ type: 'boolean',
496
+ description: 'If true, list differing files only like diff2 -q (default: true). If false, include full diff output.',
497
+ },
498
+ workspacePath: {
499
+ type: 'string',
500
+ description: 'Path to workspace directory for config detection (optional, defaults to current directory)',
501
+ },
502
+ },
503
+ required: ['sourcePath', 'targetPath'],
504
+ additionalProperties: false,
505
+ },
506
+ },
289
507
  {
290
508
  name: 'p4.changelist.create',
291
509
  description: 'Create a new changelist',
@@ -383,12 +601,15 @@ class MCPPerforceServer {
383
601
  },
384
602
  {
385
603
  name: 'p4.describe',
386
- description: 'Describe a changelist',
604
+ description: 'Describe a changelist with metadata and affected files (equivalent to p4 describe -s)',
387
605
  inputSchema: {
388
606
  type: 'object',
389
607
  properties: {
390
608
  changelist: {
391
- type: 'string',
609
+ oneOf: [
610
+ { type: 'string' },
611
+ { type: 'number' },
612
+ ],
392
613
  description: 'Changelist number (required)',
393
614
  },
394
615
  workspacePath: {
@@ -653,6 +874,147 @@ class MCPPerforceServer {
653
874
  additionalProperties: false,
654
875
  },
655
876
  },
877
+ {
878
+ name: 'p4.integrate',
879
+ description: 'Integrate files from source to target',
880
+ inputSchema: {
881
+ type: 'object',
882
+ properties: {
883
+ source: {
884
+ type: 'string',
885
+ description: 'Source filespec/path (required)',
886
+ },
887
+ target: {
888
+ type: 'string',
889
+ description: 'Target filespec/path (required)',
890
+ },
891
+ changelist: {
892
+ type: 'string',
893
+ description: 'Changelist number (optional)',
894
+ },
895
+ workspacePath: {
896
+ type: 'string',
897
+ description: 'Path to workspace directory (optional, defaults to current directory)',
898
+ },
899
+ },
900
+ required: ['source', 'target'],
901
+ additionalProperties: false,
902
+ },
903
+ },
904
+ {
905
+ name: 'p4.merge',
906
+ description: 'Merge files from source to target',
907
+ inputSchema: {
908
+ type: 'object',
909
+ properties: {
910
+ source: {
911
+ type: 'string',
912
+ description: 'Source filespec/path (required)',
913
+ },
914
+ target: {
915
+ type: 'string',
916
+ description: 'Target filespec/path (required)',
917
+ },
918
+ changelist: {
919
+ type: 'string',
920
+ description: 'Changelist number (optional)',
921
+ },
922
+ workspacePath: {
923
+ type: 'string',
924
+ description: 'Path to workspace directory (optional, defaults to current directory)',
925
+ },
926
+ },
927
+ required: ['source', 'target'],
928
+ additionalProperties: false,
929
+ },
930
+ },
931
+ {
932
+ name: 'p4.print',
933
+ description: 'Print file content from the depot',
934
+ inputSchema: {
935
+ type: 'object',
936
+ properties: {
937
+ filespec: {
938
+ type: 'string',
939
+ description: 'Depot filespec to print (required)',
940
+ },
941
+ quiet: {
942
+ type: 'boolean',
943
+ description: 'Suppress file headers (optional, defaults to true)',
944
+ },
945
+ workspacePath: {
946
+ type: 'string',
947
+ description: 'Path to workspace directory (optional, defaults to current directory)',
948
+ },
949
+ },
950
+ required: ['filespec'],
951
+ additionalProperties: false,
952
+ },
953
+ },
954
+ {
955
+ name: 'p4.fstat',
956
+ description: 'Get file metadata from depot/workspace',
957
+ inputSchema: {
958
+ type: 'object',
959
+ properties: {
960
+ filespec: {
961
+ type: 'string',
962
+ description: 'Filespec to inspect (required)',
963
+ },
964
+ max: {
965
+ type: 'number',
966
+ description: 'Maximum number of results (optional)',
967
+ },
968
+ workspacePath: {
969
+ type: 'string',
970
+ description: 'Path to workspace directory (optional, defaults to current directory)',
971
+ },
972
+ },
973
+ required: ['filespec'],
974
+ additionalProperties: false,
975
+ },
976
+ },
977
+ {
978
+ name: 'p4.streams',
979
+ description: 'List streams',
980
+ inputSchema: {
981
+ type: 'object',
982
+ properties: {
983
+ stream: {
984
+ type: 'string',
985
+ description: 'Optional stream path filter',
986
+ },
987
+ max: {
988
+ type: 'number',
989
+ description: 'Maximum number of results (optional)',
990
+ },
991
+ workspacePath: {
992
+ type: 'string',
993
+ description: 'Path to workspace directory (optional, defaults to current directory)',
994
+ },
995
+ },
996
+ additionalProperties: false,
997
+ },
998
+ },
999
+ {
1000
+ name: 'p4.stream',
1001
+ description: 'Get stream spec details',
1002
+ inputSchema: {
1003
+ type: 'object',
1004
+ properties: {
1005
+ stream: {
1006
+ type: 'string',
1007
+ description: 'Stream path/name (required)',
1008
+ },
1009
+ workspacePath: {
1010
+ type: 'string',
1011
+ description: 'Path to workspace directory (optional, defaults to current directory)',
1012
+ },
1013
+ },
1014
+ required: ['stream'],
1015
+ additionalProperties: false,
1016
+ },
1017
+ },
656
1018
  {
657
1019
  name: 'p4.grep',
658
1020
  description: 'Search for text patterns across depot files',
@@ -1029,95 +1391,10 @@ class MCPPerforceServer {
1029
1391
  log.info('Forced garbage collection');
1030
1392
  }
1031
1393
  }
1032
- // Route to appropriate tool implementation
1033
- let result;
1034
- switch (name) {
1035
- case 'p4.info':
1036
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Info(this.context, args), null, 2) }] };
1037
- case 'p4.status':
1038
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Status(this.context, args), null, 2) }] };
1039
- case 'p4.add':
1040
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Add(this.context, args), null, 2) }] };
1041
- case 'p4.edit':
1042
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Edit(this.context, args), null, 2) }] };
1043
- case 'p4.delete':
1044
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Delete(this.context, args), null, 2) }] };
1045
- case 'p4.revert':
1046
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Revert(this.context, args), null, 2) }] };
1047
- case 'p4.sync':
1048
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Sync(this.context, args), null, 2) }] };
1049
- case 'p4.opened':
1050
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Opened(this.context, args), null, 2) }] };
1051
- case 'p4.diff':
1052
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Diff(this.context, args), null, 2) }] };
1053
- case 'p4.changelist.create':
1054
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4ChangelistCreate(this.context, args), null, 2) }] };
1055
- case 'p4.changelist.update':
1056
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4ChangelistUpdate(this.context, args), null, 2) }] };
1057
- case 'p4.changelist.submit':
1058
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4ChangelistSubmit(this.context, args), null, 2) }] };
1059
- case 'p4.submit':
1060
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Submit(this.context, args), null, 2) }] };
1061
- case 'p4.describe':
1062
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Describe(this.context, args), null, 2) }] };
1063
- case 'p4.filelog':
1064
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Filelog(this.context, args), null, 2) }] };
1065
- case 'p4.clients':
1066
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Clients(this.context, args), null, 2) }] };
1067
- case 'p4.config.detect':
1068
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4ConfigDetect(this.context, args), null, 2) }] };
1069
- // High Priority Tools
1070
- case 'p4.resolve':
1071
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Resolve(this.context, args), null, 2) }] };
1072
- case 'p4.shelve':
1073
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Shelve(this.context, args), null, 2) }] };
1074
- case 'p4.unshelve':
1075
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Unshelve(this.context, args), null, 2) }] };
1076
- case 'p4.changes':
1077
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Changes(this.context, args), null, 2) }] };
1078
- case 'p4.blame':
1079
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Blame(this.context, args), null, 2) }] };
1080
- // Medium Priority Tools
1081
- case 'p4.copy':
1082
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Copy(this.context, args), null, 2) }] };
1083
- case 'p4.move':
1084
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Move(this.context, args), null, 2) }] };
1085
- case 'p4.grep':
1086
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Grep(this.context, args), null, 2) }] };
1087
- case 'p4.files':
1088
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Files(this.context, args), null, 2) }] };
1089
- case 'p4.dirs':
1090
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Dirs(this.context, args), null, 2) }] };
1091
- // Advanced/Low Priority Tools
1092
- case 'p4.users':
1093
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Users(this.context, args), null, 2) }] };
1094
- case 'p4.user':
1095
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4User(this.context, args), null, 2) }] };
1096
- case 'p4.client':
1097
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Client(this.context, args), null, 2) }] };
1098
- case 'p4.jobs':
1099
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Jobs(this.context, args), null, 2) }] };
1100
- case 'p4.job':
1101
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Job(this.context, args), null, 2) }] };
1102
- case 'p4.fixes':
1103
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Fixes(this.context, args), null, 2) }] };
1104
- case 'p4.labels':
1105
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Labels(this.context, args), null, 2) }] };
1106
- case 'p4.label':
1107
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Label(this.context, args), null, 2) }] };
1108
- case 'p4.sizes':
1109
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Sizes(this.context, args), null, 2) }] };
1110
- case 'p4.have':
1111
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Have(this.context, args), null, 2) }] };
1112
- case 'p4.where':
1113
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Where(this.context, args), null, 2) }] };
1114
- // Compliance and Security Tools
1115
- case 'p4.audit':
1116
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Audit(this.context, args), null, 2) }] };
1117
- case 'p4.compliance':
1118
- return { content: [{ type: 'text', text: JSON.stringify(await tools.p4Compliance(this.context, args), null, 2) }] };
1119
- default:
1120
- throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
1394
+ const toolArgs = (args || {});
1395
+ const result = await this.executeToolWithCaching(name, toolArgs);
1396
+ if (WRITE_TOOLS.has(name) && result && typeof result === 'object' && result.ok) {
1397
+ this.clearReadCache();
1121
1398
  }
1122
1399
  // Audit log successful operation
1123
1400
  this.context.security.logAuditEntry({
@@ -1125,11 +1402,11 @@ class MCPPerforceServer {
1125
1402
  user: result?.configUsed?.P4USER || 'unknown',
1126
1403
  client: result?.configUsed?.P4CLIENT || 'unknown',
1127
1404
  operation: name,
1128
- args: args || {},
1405
+ args: toolArgs,
1129
1406
  result: 'success',
1130
1407
  duration: Date.now() - startTime,
1131
1408
  });
1132
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1409
+ return this.toTextResponse(result);
1133
1410
  }
1134
1411
  catch (error) {
1135
1412
  const duration = Date.now() - startTime;
@@ -1171,47 +1448,53 @@ if (require.main === module) {
1171
1448
  }
1172
1449
  // Handle help flag
1173
1450
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
1174
- console.log(`
1175
- MCP Perforce Server v${packageJson.version}
1176
- ===========================
1177
-
1178
- A production-ready MCP (Model Context Protocol) server for Perforce operations.
1179
-
1180
- Usage:
1181
- mcp-perforce-server Start the MCP server (stdio transport)
1182
- mcp-perforce-server --help Show this help message
1183
- mcp-perforce-server --version Show version information
1184
-
1185
- Environment Variables:
1186
- P4_READONLY_MODE=false Enable write operations (default: true)
1187
- P4_DISABLE_DELETE=false Enable delete operations (default: true)
1188
- P4_PATH=/path/to/p4 Custom p4 executable path
1189
- P4CONFIG=.p4config Config file name (default: .p4config)
1190
- LOG_LEVEL=info Logging level: error,warn,info,debug
1191
-
1192
- Compliance & Security:
1193
- P4_ENABLE_AUDIT_LOGGING=true Enable audit logging (default: false)
1194
- P4_ENABLE_RATE_LIMITING=false Disable rate limiting (default: true)
1195
- P4_ENABLE_MEMORY_LIMITS=false Disable memory limits (default: true)
1196
- P4_ENABLE_INPUT_SANITIZATION=false Disable input sanitization (default: true)
1197
- P4_MAX_MEMORY_MB=1024 Memory limit in MB (default: 512)
1198
- P4_AUDIT_RETENTION_DAYS=365 Audit log retention days (default: 90)
1199
- P4_RATE_LIMIT_REQUESTS=100 Max requests per window (default: 100)
1200
- P4_RATE_LIMIT_WINDOW_MS=600000 Rate limit window ms (default: 10min)
1201
- P4_RATE_LIMIT_BLOCK_MS=3600000 Rate limit block duration ms (default: 1hr)
1202
-
1203
- Configuration:
1204
- Place a .p4config file in your project root or parent directories:
1205
-
1206
- P4PORT=your-server:1666
1207
- P4USER=your-username
1208
- P4CLIENT=your-workspace-name
1209
-
1210
- IDE Integration:
1211
- Configure your IDE's MCP client to use this server.
1212
- See README.md for VS Code and Cursor setup instructions.
1213
-
1214
- For more information: https://github.com/iPraBhu/mcp-perforce-server
1451
+ console.log(`
1452
+ MCP Perforce Server v${packageJson.version}
1453
+ ===========================
1454
+
1455
+ A production-ready MCP (Model Context Protocol) server for Perforce operations.
1456
+
1457
+ Usage:
1458
+ mcp-perforce-server Start the MCP server (stdio transport)
1459
+ mcp-perforce-server --help Show this help message
1460
+ mcp-perforce-server --version Show version information
1461
+
1462
+ Environment Variables:
1463
+ P4_READONLY_MODE=false Enable write operations (default: read-only enabled)
1464
+ P4_DISABLE_DELETE=false Enable delete operations (default: delete disabled)
1465
+ P4_PATH=/path/to/p4 Custom p4 executable path
1466
+ P4CONFIG=.p4config Config file name (default: .p4config)
1467
+ LOG_LEVEL=info Logging level: error,warn,info,debug
1468
+ P4_PERFORMANCE_MODE=fast Performance profile: fast|balanced|secure (default: fast)
1469
+ P4_TIMEOUT_MS=5000 Command timeout in milliseconds (default by mode)
1470
+ P4_PRETTY_JSON=true Pretty-print JSON responses (default: compact JSON)
1471
+ P4_RESPONSE_CACHE=false Disable read-result cache (default: enabled)
1472
+ P4_RESPONSE_CACHE_TTL_MS=5000 Cache TTL in ms (default by mode: fast 5000, balanced 3000, secure 1000)
1473
+ P4_RESPONSE_CACHE_MAX_ENTRIES=400 Max cached responses (default by mode)
1474
+
1475
+ Compliance & Security:
1476
+ P4_ENABLE_AUDIT_LOGGING=true|false Override audit logging (default in fast mode: false)
1477
+ P4_ENABLE_RATE_LIMITING=true|false Override rate limiting (default in fast mode: false)
1478
+ P4_ENABLE_MEMORY_LIMITS=true|false Override memory limits (default in fast mode: false)
1479
+ P4_ENABLE_INPUT_SANITIZATION=false Disable input sanitization (default: enabled)
1480
+ P4_MAX_MEMORY_MB=1024 Memory limit in MB (default: 512)
1481
+ P4_AUDIT_RETENTION_DAYS=365 Audit log retention days (default: 90)
1482
+ P4_RATE_LIMIT_REQUESTS=100 Max requests per window (default: 100)
1483
+ P4_RATE_LIMIT_WINDOW_MS=600000 Rate limit window ms (default: 10min)
1484
+ P4_RATE_LIMIT_BLOCK_MS=3600000 Rate limit block duration ms (default: 1hr)
1485
+
1486
+ Configuration:
1487
+ Place a .p4config file in your project root or parent directories:
1488
+
1489
+ P4PORT=your-server:1666
1490
+ P4USER=your-username
1491
+ P4CLIENT=your-workspace-name
1492
+
1493
+ IDE Integration:
1494
+ Configure your IDE's MCP client to use this server.
1495
+ See README.md for VS Code and Cursor setup instructions.
1496
+
1497
+ For more information: https://github.com/iPraBhu/mcp-perforce-server
1215
1498
  `);
1216
1499
  process.exit(0);
1217
1500
  }