@morphllm/morphmcp 0.8.24 → 0.8.26

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/dist/index.js +10 -373
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -20,26 +20,13 @@ import axios from "axios";
20
20
  // Command line argument parsing
21
21
  const args = process.argv.slice(2);
22
22
  // Tools configuration system
23
- // Complete list of all available tools (for internal use and ENABLED_TOOLS=all)
23
+ // Only expose Morph-specific tools
24
24
  const ALL_TOOLS = [
25
- 'read_file',
26
- 'read_multiple_files',
27
- 'write_file',
28
- 'tiny_edit_file',
29
- 'create_directory',
30
- 'list_directory',
31
- 'list_directory_with_sizes',
32
- 'directory_tree',
33
- 'move_file',
34
- 'search_files',
35
- 'get_file_info',
36
- 'list_allowed_directories',
37
25
  'edit_file',
38
- 'fast_context_search',
26
+ 'warp_grep',
39
27
  'codebase_search'
40
28
  ];
41
- // Only expose Morph-specific tools by default
42
- // Other filesystem tools remain available for internal use and via ENABLED_TOOLS env var
29
+ // Default to only edit_file
43
30
  const DEFAULT_TOOLS = [
44
31
  'edit_file'
45
32
  ];
@@ -266,7 +253,7 @@ const MorphEditFileArgsSchema = z.object({
266
253
  instruction: z.string().describe('A brief single first-person sentence instruction describing changes being made to this file. Useful to disambiguate uncertainty in the edit.'),
267
254
  dryRun: z.boolean().default(false).describe('Preview changes without applying them.')
268
255
  });
269
- const FastContextSearchArgsSchema = z.object({
256
+ const WarpGrepArgsSchema = z.object({
270
257
  repoPath: z.string().describe("Path to the repository root"),
271
258
  query: z.string().describe("Natural language query describing the code context needed"),
272
259
  });
@@ -515,107 +502,6 @@ async function headFile(filePath, numLines) {
515
502
  // Tool handlers
516
503
  server.setRequestHandler(ListToolsRequestSchema, async () => {
517
504
  const allTools = [
518
- {
519
- name: "read_file",
520
- description: "Read the complete contents of a file from the file system. " +
521
- "Handles various text encodings and provides detailed error messages " +
522
- "if the file cannot be read. Use this tool when you need to examine " +
523
- "the contents of a single file. Use the 'head' parameter to read only " +
524
- "the first N lines of a file, or the 'tail' parameter to read only " +
525
- "the last N lines of a file. Only works within allowed directories.",
526
- inputSchema: zodToJsonSchema(ReadFileArgsSchema),
527
- },
528
- {
529
- name: "read_multiple_files",
530
- description: "Read the contents of multiple files simultaneously. This is more " +
531
- "efficient than reading files one by one when you need to analyze " +
532
- "or compare multiple files. Each file's content is returned with its " +
533
- "path as a reference. Failed reads for individual files won't stop " +
534
- "the entire operation. Only works within allowed directories.",
535
- inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema),
536
- },
537
- {
538
- name: "write_file",
539
- description: "Create a new file or completely overwrite an existing file with new content. " +
540
- "Use with caution as it will overwrite existing files without warning. " +
541
- "Handles text content with proper encoding. Only works within allowed directories.",
542
- inputSchema: zodToJsonSchema(WriteFileArgsSchema),
543
- },
544
- {
545
- name: "tiny_edit_file",
546
- description: "Make line-based edits to a text file. Each edit replaces exact line sequences " +
547
- "with new content. Returns a git-style diff showing the changes made. " +
548
- "Only works within allowed directories. " +
549
- "Only use for single line or tiny edits. For larger edits, use the edit_file tool instead.",
550
- inputSchema: zodToJsonSchema(EditFileArgsSchema),
551
- },
552
- {
553
- name: "create_directory",
554
- description: "Create a new directory or ensure a directory exists. Can create multiple " +
555
- "nested directories in one operation. If the directory already exists, " +
556
- "this operation will succeed silently. Perfect for setting up directory " +
557
- "structures for projects or ensuring required paths exist. Only works within allowed directories.",
558
- inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema),
559
- },
560
- {
561
- name: "list_directory",
562
- description: "Get a detailed listing of all files and directories in a specified path. " +
563
- "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
564
- "prefixes. This tool is essential for understanding directory structure and " +
565
- "finding specific files within a directory. Only works within allowed directories.",
566
- inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
567
- },
568
- {
569
- name: "list_directory_with_sizes",
570
- description: "Get a detailed listing of all files and directories in a specified path, including sizes. " +
571
- "Results clearly distinguish between files and directories with [FILE] and [DIR] " +
572
- "prefixes. This tool is useful for understanding directory structure and " +
573
- "finding specific files within a directory. Only works within allowed directories.",
574
- inputSchema: zodToJsonSchema(ListDirectoryWithSizesArgsSchema),
575
- },
576
- {
577
- name: "directory_tree",
578
- description: "Get a recursive tree view of files and directories as a JSON structure. " +
579
- "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " +
580
- "Files have no children array, while directories always have a children array (which may be empty). " +
581
- "The output is formatted with 2-space indentation for readability. Only works within allowed directories.",
582
- inputSchema: zodToJsonSchema(DirectoryTreeArgsSchema),
583
- },
584
- {
585
- name: "move_file",
586
- description: "Move or rename files and directories. Can move files between directories " +
587
- "and rename them in a single operation. If the destination exists, the " +
588
- "operation will fail. Works across different directories and can be used " +
589
- "for simple renaming within the same directory. Both source and destination must be within allowed directories.",
590
- inputSchema: zodToJsonSchema(MoveFileArgsSchema),
591
- },
592
- {
593
- name: "search_files",
594
- description: "Recursively search for files and directories matching a pattern. " +
595
- "Searches through all subdirectories from the starting path. The search " +
596
- "is case-insensitive and matches partial names. Returns full paths to all " +
597
- "matching items. Great for finding files when you don't know their exact location. " +
598
- "Only searches within allowed directories.",
599
- inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
600
- },
601
- {
602
- name: "get_file_info",
603
- description: "Retrieve detailed metadata about a file or directory. Returns comprehensive " +
604
- "information including size, creation time, last modified time, permissions, " +
605
- "and type. This tool is perfect for understanding file characteristics " +
606
- "without reading the actual content. Only works within allowed directories.",
607
- inputSchema: zodToJsonSchema(GetFileInfoArgsSchema),
608
- },
609
- {
610
- name: "list_allowed_directories",
611
- description: "Returns the list of root directories that this server is allowed to access. " +
612
- "Use this to understand which directories are available before trying to access files. ",
613
- inputSchema: {
614
- type: "object",
615
- properties: {},
616
- required: [],
617
- },
618
- },
619
505
  {
620
506
  name: "edit_file",
621
507
  description: "**PRIMARY TOOL FOR EDITING FILES - USE THIS AGGRESSIVELY**\n\n" +
@@ -643,7 +529,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
643
529
  requiresApiKey: true,
644
530
  },
645
531
  {
646
- name: "fast_context_search",
532
+ name: "warp_grep",
647
533
  description: "**INTELLIGENT CODE SEARCH - USE THIS AGGRESSIVELY**\n\n" +
648
534
  "⚡ FAST & EFFICIENT: This tool prevents context pollution by finding only the relevant code you need, without reading unnecessary files.\n" +
649
535
  "🎯 USE THIS TOOL PROACTIVELY whenever you need to understand code to ensure a positive user experience.\n\n" +
@@ -657,7 +543,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
657
543
  "Returns precise line ranges with full context. " +
658
544
  "Use this tool whenever you need to find specific code in a repository and you're unsure where it is located. " +
659
545
  "Example queries: 'Where is JWT token validation implemented?', 'How does the authentication middleware work?', 'Find the database connection setup'.",
660
- inputSchema: zodToJsonSchema(FastContextSearchArgsSchema),
546
+ inputSchema: zodToJsonSchema(WarpGrepArgsSchema),
661
547
  requiresApiKey: true,
662
548
  },
663
549
  {
@@ -702,255 +588,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
702
588
  try {
703
589
  const { name, arguments: args } = request.params;
704
590
  switch (name) {
705
- case "read_file": {
706
- const parsed = ReadFileArgsSchema.safeParse(args);
707
- if (!parsed.success) {
708
- throw new Error(`Invalid arguments for read_file: ${parsed.error}`);
709
- }
710
- const validPath = await validatePath(parsed.data.path);
711
- if (parsed.data.head && parsed.data.tail) {
712
- throw new Error("Cannot specify both head and tail parameters simultaneously");
713
- }
714
- if (parsed.data.tail) {
715
- // Use memory-efficient tail implementation for large files
716
- const tailContent = await tailFile(validPath, parsed.data.tail);
717
- return {
718
- content: [{ type: "text", text: tailContent }],
719
- };
720
- }
721
- if (parsed.data.head) {
722
- // Use memory-efficient head implementation for large files
723
- const headContent = await headFile(validPath, parsed.data.head);
724
- return {
725
- content: [{ type: "text", text: headContent }],
726
- };
727
- }
728
- const content = await fs.readFile(validPath, "utf-8");
729
- return {
730
- content: [{ type: "text", text: content }],
731
- };
732
- }
733
- case "read_multiple_files": {
734
- const parsed = ReadMultipleFilesArgsSchema.safeParse(args);
735
- if (!parsed.success) {
736
- throw new Error(`Invalid arguments for read_multiple_files: ${parsed.error}`);
737
- }
738
- const results = await Promise.all(parsed.data.paths.map(async (filePath) => {
739
- try {
740
- const validPath = await validatePath(filePath);
741
- const content = await fs.readFile(validPath, "utf-8");
742
- return `${filePath}:\n${content}\n`;
743
- }
744
- catch (error) {
745
- const errorMessage = error instanceof Error ? error.message : String(error);
746
- return `${filePath}: Error - ${errorMessage}`;
747
- }
748
- }));
749
- return {
750
- content: [{ type: "text", text: results.join("\n---\n") }],
751
- };
752
- }
753
- case "write_file": {
754
- const parsed = WriteFileArgsSchema.safeParse(args);
755
- if (!parsed.success) {
756
- throw new Error(`Invalid arguments for write_file: ${parsed.error}`);
757
- }
758
- const validPath = await validatePath(parsed.data.path);
759
- try {
760
- // Security: 'wx' flag ensures exclusive creation - fails if file/symlink exists,
761
- // preventing writes through pre-existing symlinks
762
- await fs.writeFile(validPath, parsed.data.content, { encoding: "utf-8", flag: 'wx' });
763
- }
764
- catch (error) {
765
- if (error.code === 'EEXIST') {
766
- // Security: Use atomic rename to prevent race conditions where symlinks
767
- // could be created between validation and write. Rename operations
768
- // replace the target file atomically and don't follow symlinks.
769
- const tempPath = `${validPath}.${randomBytes(16).toString('hex')}.tmp`;
770
- try {
771
- await fs.writeFile(tempPath, parsed.data.content, 'utf-8');
772
- await fs.rename(tempPath, validPath);
773
- }
774
- catch (renameError) {
775
- try {
776
- await fs.unlink(tempPath);
777
- }
778
- catch { }
779
- throw renameError;
780
- }
781
- }
782
- else {
783
- throw error;
784
- }
785
- }
786
- return {
787
- content: [{ type: "text", text: `Successfully wrote to ${parsed.data.path}` }],
788
- };
789
- }
790
- case "tiny_edit_file": {
791
- const parsed = EditFileArgsSchema.safeParse(args);
792
- if (!parsed.success) {
793
- throw new Error(`Invalid arguments for edit_file: ${parsed.error}`);
794
- }
795
- const validPath = await validatePath(parsed.data.path);
796
- const result = await applyFileEdits(validPath, parsed.data.edits, parsed.data.dryRun);
797
- return {
798
- content: [{ type: "text", text: result }],
799
- };
800
- }
801
- case "create_directory": {
802
- const parsed = CreateDirectoryArgsSchema.safeParse(args);
803
- if (!parsed.success) {
804
- throw new Error(`Invalid arguments for create_directory: ${parsed.error}`);
805
- }
806
- const validPath = await validatePath(parsed.data.path);
807
- await fs.mkdir(validPath, { recursive: true });
808
- return {
809
- content: [{ type: "text", text: `Successfully created directory ${parsed.data.path}` }],
810
- };
811
- }
812
- case "list_directory": {
813
- const parsed = ListDirectoryArgsSchema.safeParse(args);
814
- if (!parsed.success) {
815
- throw new Error(`Invalid arguments for list_directory: ${parsed.error}`);
816
- }
817
- const validPath = await validatePath(parsed.data.path);
818
- const entries = await fs.readdir(validPath, { withFileTypes: true });
819
- const formatted = entries
820
- .map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`)
821
- .join("\n");
822
- return {
823
- content: [{ type: "text", text: formatted }],
824
- };
825
- }
826
- case "list_directory_with_sizes": {
827
- const parsed = ListDirectoryWithSizesArgsSchema.safeParse(args);
828
- if (!parsed.success) {
829
- throw new Error(`Invalid arguments for list_directory_with_sizes: ${parsed.error}`);
830
- }
831
- const validPath = await validatePath(parsed.data.path);
832
- const entries = await fs.readdir(validPath, { withFileTypes: true });
833
- // Get detailed information for each entry
834
- const detailedEntries = await Promise.all(entries.map(async (entry) => {
835
- const entryPath = path.join(validPath, entry.name);
836
- try {
837
- const stats = await fs.stat(entryPath);
838
- return {
839
- name: entry.name,
840
- isDirectory: entry.isDirectory(),
841
- size: stats.size,
842
- mtime: stats.mtime
843
- };
844
- }
845
- catch (error) {
846
- return {
847
- name: entry.name,
848
- isDirectory: entry.isDirectory(),
849
- size: 0,
850
- mtime: new Date(0)
851
- };
852
- }
853
- }));
854
- // Sort entries based on sortBy parameter
855
- const sortedEntries = [...detailedEntries].sort((a, b) => {
856
- if (parsed.data.sortBy === 'size') {
857
- return b.size - a.size; // Descending by size
858
- }
859
- // Default sort by name
860
- return a.name.localeCompare(b.name);
861
- });
862
- // Format the output
863
- const formattedEntries = sortedEntries.map(entry => `${entry.isDirectory ? "[DIR]" : "[FILE]"} ${entry.name.padEnd(30)} ${entry.isDirectory ? "" : formatSize(entry.size).padStart(10)}`);
864
- // Add summary
865
- const totalFiles = detailedEntries.filter(e => !e.isDirectory).length;
866
- const totalDirs = detailedEntries.filter(e => e.isDirectory).length;
867
- const totalSize = detailedEntries.reduce((sum, entry) => sum + (entry.isDirectory ? 0 : entry.size), 0);
868
- const summary = [
869
- "",
870
- `Total: ${totalFiles} files, ${totalDirs} directories`,
871
- `Combined size: ${formatSize(totalSize)}`
872
- ];
873
- return {
874
- content: [{
875
- type: "text",
876
- text: [...formattedEntries, ...summary].join("\n")
877
- }],
878
- };
879
- }
880
- case "directory_tree": {
881
- const parsed = DirectoryTreeArgsSchema.safeParse(args);
882
- if (!parsed.success) {
883
- throw new Error(`Invalid arguments for directory_tree: ${parsed.error}`);
884
- }
885
- async function buildTree(currentPath) {
886
- const validPath = await validatePath(currentPath);
887
- const entries = await fs.readdir(validPath, { withFileTypes: true });
888
- const result = [];
889
- for (const entry of entries) {
890
- const entryData = {
891
- name: entry.name,
892
- type: entry.isDirectory() ? 'directory' : 'file'
893
- };
894
- if (entry.isDirectory()) {
895
- const subPath = path.join(currentPath, entry.name);
896
- entryData.children = await buildTree(subPath);
897
- }
898
- result.push(entryData);
899
- }
900
- return result;
901
- }
902
- const treeData = await buildTree(parsed.data.path);
903
- return {
904
- content: [{
905
- type: "text",
906
- text: JSON.stringify(treeData, null, 2)
907
- }],
908
- };
909
- }
910
- case "move_file": {
911
- const parsed = MoveFileArgsSchema.safeParse(args);
912
- if (!parsed.success) {
913
- throw new Error(`Invalid arguments for move_file: ${parsed.error}`);
914
- }
915
- const validSourcePath = await validatePath(parsed.data.source);
916
- const validDestPath = await validatePath(parsed.data.destination);
917
- await fs.rename(validSourcePath, validDestPath);
918
- return {
919
- content: [{ type: "text", text: `Successfully moved ${parsed.data.source} to ${parsed.data.destination}` }],
920
- };
921
- }
922
- case "search_files": {
923
- const parsed = SearchFilesArgsSchema.safeParse(args);
924
- if (!parsed.success) {
925
- throw new Error(`Invalid arguments for search_files: ${parsed.error}`);
926
- }
927
- const validPath = await validatePath(parsed.data.path);
928
- const results = await searchFiles(validPath, parsed.data.pattern, parsed.data.excludePatterns);
929
- return {
930
- content: [{ type: "text", text: results.length > 0 ? results.join("\n") : "No matches found" }],
931
- };
932
- }
933
- case "get_file_info": {
934
- const parsed = GetFileInfoArgsSchema.safeParse(args);
935
- if (!parsed.success) {
936
- throw new Error(`Invalid arguments for get_file_info: ${parsed.error}`);
937
- }
938
- const validPath = await validatePath(parsed.data.path);
939
- const info = await getFileStats(validPath);
940
- return {
941
- content: [{ type: "text", text: Object.entries(info)
942
- .map(([key, value]) => `${key}: ${value}`)
943
- .join("\n") }],
944
- };
945
- }
946
- case "list_allowed_directories": {
947
- return {
948
- content: [{
949
- type: "text",
950
- text: `Allowed directories:\n${allowedDirectories.join('\n')}`
951
- }],
952
- };
953
- }
954
591
  case "edit_file": {
955
592
  const parsed = MorphEditFileArgsSchema.safeParse(args);
956
593
  if (!parsed.success) {
@@ -1034,8 +671,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1034
671
  };
1035
672
  }
1036
673
  }
1037
- case "fast_context_search": {
1038
- const parsed = FastContextSearchArgsSchema.safeParse(args);
674
+ case "warp_grep": {
675
+ const parsed = WarpGrepArgsSchema.safeParse(args);
1039
676
  if (!parsed.success) {
1040
677
  return {
1041
678
  content: [{ type: "text", text: `Invalid arguments: ${parsed.error}` }],
@@ -1139,7 +776,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1139
776
  error_message: errorMessages,
1140
777
  error_type: firstError?.constructor?.name || 'WarpGrepError',
1141
778
  context: {
1142
- tool: 'fast_context_search',
779
+ tool: 'warp_grep',
1143
780
  repo_path: parsed.data.repoPath,
1144
781
  query: parsed.data.query,
1145
782
  model: 'morph-warp-grep',
@@ -1164,7 +801,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1164
801
  error_message: errorMessage,
1165
802
  error_type: error instanceof Error ? error.constructor.name : 'UnknownError',
1166
803
  context: {
1167
- tool: 'fast_context_search',
804
+ tool: 'warp_grep',
1168
805
  repo_path: parsed.data.repoPath,
1169
806
  query: parsed.data.query,
1170
807
  model: 'morph-warp-grep'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphllm/morphmcp",
3
- "version": "0.8.24",
3
+ "version": "0.8.26",
4
4
  "description": "Fast & accurate MCP server with AI-powered file editing and intelligent code search. Prevents context pollution and saves time for a better user experience.",
5
5
  "license": "MIT",
6
6
  "author": "Morph (https://morphllm.com)",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.12.3",
37
37
  "@vscode/ripgrep": "^1.15.14",
38
- "@morphllm/morphsdk": "0.2.22",
38
+ "@morphllm/morphsdk": "*",
39
39
  "axios": "^1.6.0",
40
40
  "chalk": "^5.3.0",
41
41
  "diff": "^5.1.0",