UNPKG

mcp-files

Version:

Enables agents to quickly find and edit code in a codebase with surgical precision. Find symbols, edit them everywhere

62 lines (55 loc) 18.3 kB
#!/usr/bin/env node var le=Object.create;var F=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var me=Object.getOwnPropertyNames;var fe=Object.getPrototypeOf,pe=Object.prototype.hasOwnProperty;var A=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var ue=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of me(t))!pe.call(e,o)&&o!==n&&F(e,o,{get:()=>t[o],enumerable:!(r=ce(t,o))||r.enumerable});return e};var de=(e,t,n)=>(n=e!=null?le(fe(e)):{},ue(t||!e||!e.__esModule?F(n,"default",{value:e,enumerable:!0}):n,e));var H=A((Qt,K)=>{var v=class{constructor(t){this.value=t,this.next=void 0}},k=class{constructor(){this.clear()}enqueue(t){let n=new v(t);this._head?(this._tail.next=n,this._tail=n):(this._head=n,this._tail=n),this._size++}dequeue(){let t=this._head;if(t)return this._head=this._head.next,this._size--,t.value}clear(){this._head=void 0,this._tail=void 0,this._size=0}get size(){return this._size}*[Symbol.iterator](){let t=this._head;for(;t;)yield t.value,t=t.next}};K.exports=k});var Z=A((en,V)=>{"use strict";var Be=H(),We=e=>{if(!((Number.isInteger(e)||e===1/0)&&e>0))throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=new Be,n=0,r=()=>{n--,t.size>0&&t.dequeue()()},o=async(m,l,...s)=>{n++;let p=(async()=>m(...s))();l(p);try{await p}catch{}r()},i=(m,l,...s)=>{t.enqueue(o.bind(null,m,l,...s)),(async()=>(await Promise.resolve(),n<e&&t.size>0&&t.dequeue()()))()},c=(m,...l)=>new Promise(s=>{i(m,s,...l)});return Object.defineProperties(c,{activeCount:{get:()=>n},pendingCount:{get:()=>t.size},clearQueue:{value:()=>{t.clear()}}}),c};V.exports=We});import yt from"lodash";import{ZodError as bt}from"zod";var he={PORT:ge("PORT",4657),TRANSPORT:j("TRANSPORT","stdio"),DEBUG:L("DEBUG",!1),OVERRIDE_S_R:L("OVERRIDE_S_R",!1),CLI:!1};function j(e,t){return process.env[e]??String(t)}function ge(e,t){return Number.parseFloat(j(e,t))}function L(e,t){return j(e,t)==="true"}var f=he;import Se from"fs";import{execSync as ye}from"child_process";import S from"fs";import be from"lodash";import{dirname as D,isAbsolute as we,resolve as M}from"path";import{fileURLToPath as $e}from"url";var B=process.env.WORKSPACE_FOLDER_PATHS||process.cwd(),b={CWD:B,REPO:M(D($e(import.meta.url)),".."),resolve(e,t=B){return we(e)?e:M(t,e)},readFile(e,t){if(!b.exists(e)){if(be.isNil(t))throw new Error(`File not found: ${e}`);return t}return S.readFileSync(e,"utf-8")},writeFile(e,t){b.mkdirp(D(e)),S.writeFileSync(e,t,"utf-8")},mkdirp(e){b.exists(e)||S.mkdirSync(e,{recursive:!0})},ext(e){let t=e.match(/\.(\w{2,5})$/);return t?t[1]:""},isFile(e){return!!b.ext(e)},exists(e){return S.existsSync(e)},keysOf(e){return Object.keys(e)},trimLines(e){return e.replace(/^ +\n?/gm,"").trim()},clamp(e,t,n){return Math.max(t,Math.min(e,n))},truncate(e,t,n="..."){return e.length>t?e.slice(0,t)+n:e},execSync:ye,int:_e};function _e(e,t){let n=Number.parseInt(e,10);return Number.isNaN(n)?t:n}var a=b;var xe=a.resolve("package.json",a.REPO),{default:I}=await import(xe,{with:{type:"json"}}),u={...I,version:I.version,author:I.homepage?.split("/")[3]||"unknown"};var Te=a.resolve("./logs.json",a.REPO);function Ee(e,t,n){let r={timestamp:new Date().toISOString(),level:e,version:u.version,cwd:a.CWD,message:t,...n};return JSON.stringify(r)+` `}function T(e,t,n){if(f.DEBUG)try{let r=Ee(e,t,n);Se.appendFileSync(Te,r,"utf-8")}catch(r){console.error("Logger error:",r)}}var Re={log:(e,t)=>T("LOG",e,t),info:(e,t)=>T("INFO",e,t),warn:(e,t)=>T("WARN",e,t),error:(e,t)=>T("ERROR",e,t)},E=Re;import w from"lodash";import{z as P}from"zod";var Oe=d({id:"import_symbol",schema:P.object({module_path:P.string().min(1).describe('Module path to import (e.g., "lodash", "./utils", "@package/name")'),property:P.string().optional().describe("Only the given property from the module is dumped")}),description:"Import and inspect JavaScript/TypeScript modules ala require(), or import()",isReadOnly:!0,isEnabled:f.DEBUG,fromArgs:([e,t])=>({module_path:e,property:t||void 0}),handler:async e=>{let{module_path:t,property:n}=e,o=await import(t.startsWith(".")?a.resolve(t):t);o.default&&(o=o.default);let i=n?w.get(o,n):o;if(i===void 0)throw new Error(`Property '${n}' not found in module '${t}'`);let c=[];return c.push(`=== ${t}${n?`.${n}`:""} ===`),c.push(je(i)),c.join(` `)}}),U=Oe,W=300;function je(e){let t=typeof e,n=[];if(t==="object"&&e!==null){let r=Object.keys(e).sort();if(r.length>0){if(w.isArray(e)){let o=typeof e[0];n.push(`${o}[${e.length}]`)}else w.isPlainObject(e)?n.push("object"):n.push(`object (${e.constructor.name})`);return r.forEach(o=>{o.startsWith("_")||n.push(`${o}: ${G(e[o])}`)}),n.join(` `)}}return G(e)}function G(e){let t=typeof e;if(t==="function")return Ie(e)?`class ${Pe(e)}`:`function ${z(e)}`;if(w.isArray(e)){let n=JSON.stringify(e);return n.length<=W?n:`${typeof e[0]}[${e.length}]`}if(w.isPlainObject(e)){let n=JSON.stringify(e);return n.length<=W?n:`object (${Object.keys(e).length} properties)`}return t==="object"&&e!==null?e.constructor.name:JSON.stringify(e)}function Ie(e){return e.toString().startsWith("class ")}function z(e){try{let n=e.toString().match(/\(([^)]*)\)/);return n?.[1]?`(${n[1].split(",").map(o=>o.trim().split("=")[0]?.trim()).filter(Boolean).join(", ")})`:"(...)"}catch{return"(...)"}}function Pe(e){try{let t=e.toString().match(/constructor\s*\(([^)]*)\)/);return t?.[1]?`(${t[1].split(",").map(r=>r.trim().split("=")?.[0]?.trim()).filter(r=>r).join(", ")})`:z(e)}catch{return"(...)"}}import{z as $}from"zod";var q=2,Ne=$.object({file_path:$.string().min(1).describe("Path to the file"),from_line:$.number().int().min(1).describe("Starting line number (1-based)"),text:$.string().describe("Text to insert"),to_line:$.number().int().min(1).optional().describe("Replace up to this line number (1-based, inclusive). If omitted only inserts")}),ve=d({id:"insert_text",schema:Ne,description:a.trimLines(` Insert or replace text at precise line ranges in files - Ideal for direct line-number operations (from code citations like 12:15:file.ts) and large files where context-heavy editing is inefficient. - TIP: Combine with read_symbol to edit any symbol anywhere without knowing its file or line range! `),isReadOnly:!1,fromArgs:([e,t,n,r])=>({file_path:e,from_line:a.int(t),text:n,to_line:a.int(r)}),handler:e=>{let t=a.resolve(e.file_path),n=a.readFile(t),r=ke(n,e.from_line,e.text,e.to_line);return a.writeFile(t,r),Ce(r,e.from_line,e.text,t)}}),X=ve;function ke(e,t,n,r){let o=r??t;if(o<t)throw new Error(`Invalid line range: to_line (${o}) cannot be less than from_line (${t})`);let i=e===""?[]:e.split(` `);if(t>i.length+1)throw new Error(`from_line ${t} is beyond file length (${i.length} lines). Maximum allowed: ${i.length+1}`);if(r&&o>i.length)throw new Error(`to_line ${o} is beyond file length (${i.length} lines). Maximum allowed: ${i.length}`);let c=r?o-t+1:0,m=n.split(` `);return i.splice(t-1,c,...m),i.join(` `)}function Ce(e,t,n,r){let o=e.split(` `),i=n.split(` `),c=Math.max(0,t-1-q),m=Math.min(o.length,t-1+i.length+q);return`${`=== ${c+1}:${m+1}:${r} ===`} ${o.slice(c,m).join(` `)}`}import Fe from"lodash";import{basename as Ae}from"path";import{z as N}from"zod";var Le=d({id:"os_notification",schema:N.object({message:N.string().min(1).describe("The notification message to display"),title:N.string().optional().describe("Defaults to current project, generally omit")}),description:"Send OS notifications using native notification systems.",isReadOnly:!0,fromArgs:([e,t])=>({message:e,title:t||void 0}),handler:e=>{let{message:t,title:n=Ae(a.CWD)}=e,r=Me(),o=r.cmd(n,t);return a.execSync(o,{stdio:"ignore"}),`Notification would have been sent via ${r.check} with title "${n}" and message "${t}"`}}),J=Le,De=[{check:"notify-send",cmd:(e,t)=>`notify-send "${e}" "${t}"`},{check:"osascript",cmd:(e,t)=>`osascript -e 'display notification "${t}" with title "${e}"'`},{check:"powershell",cmd:(e,t)=>`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; \\$notify = New-Object System.Windows.Forms.NotifyIcon; \\$notify.Icon = [System.Drawing.SystemIcons]::Information; \\$notify.BalloonTipTitle = '${e}'; \\$notify.BalloonTipText = '${t}'; \\$notify.Visible = \\$true; \\$notify.ShowBalloonTip(5000); Start-Sleep -Seconds 2; \\$notify.Dispose()"`},{check:"powershell.exe",cmd:(e,t)=>`powershell.exe -Command "Add-Type -AssemblyName System.Windows.Forms; \\$notify = New-Object System.Windows.Forms.NotifyIcon; \\$notify.Icon = [System.Drawing.SystemIcons]::Information; \\$notify.BalloonTipTitle = '${e}'; \\$notify.BalloonTipText = '${t}'; \\$notify.Visible = \\$true; \\$notify.ShowBalloonTip(5000); Start-Sleep -Seconds 2; \\$notify.Dispose()"`},{check:"wsl-notify-send.exe",cmd:(e,t)=>`wsl-notify-send.exe --category "${e}" "${t}"`}],Me=Fe.memoize(()=>{for(let e of De)try{return a.execSync(`command -v ${e.check}`,{stdio:"ignore"}),e}catch{}throw new Error("No notification method available. Install notify-send, osascript, powershell, or wsl-notify-send")});var ee=de(Z(),1);import Ge from"fast-glob";import Ue from"fs/promises";import Q from"lodash";import{z as _}from"zod";var ze=32,qe=10*1024*1024,Xe=2e3,Je=15e3,Ke=200,He=20,Y=5,C=["d.ts","ts","tsx","js","jsx","mjs","cjs","cts","java","cs","cpp","c","h","hpp","cc","go","rs","php","swift","scss","css","less","graphql","gql","prisma","proto"],Ve=["node_modules","dist","build","out",".git"],Ze=["test","tests","examples","bin","runtime"],Ye=["*.test.*","*.spec.*","_*","*.min.*"],Qe=/\b(class|interface|type|function|enum|namespace|module|model|declare|abstract|const|extends|implements)\b/gi,et=d({id:"read_symbol",schema:_.object({symbol:_.string().min(1).describe("Symbol name to find (functions, classes, types, etc.), case-sensitive, supports * for wildcard"),file_paths:_.array(_.string().min(1)).optional().describe('File paths to search (supports relative and glob). Defaults to "." (current directory). IMPORTANT: Be specific with paths when possible, minimize broad patterns like "node_modules/**" to avoid mismatches'),limit:_.number().optional().describe(`Maximum number of results to return. Defaults to ${Y}`)}),description:"Find and extract symbol block by name from files, supports a lot of file formats (like TS, JS, GraphQL, CSS and most that use braces for blocks). Uses streaming with concurrency control for better performance",isReadOnly:!0,fromArgs:([e,...t])=>({symbol:e,file_paths:t.length?t:void 0}),handler:async e=>{let{symbol:t,file_paths:n=[],limit:r=Y}=e;n.length||n.push(".");let o=n.map(tt),i=[],c=0;try{for await(let l of rt(t,o))if(c++,i.push(l),i.length>=He)break}catch(l){if(!i.length)throw l}if(!i.length)throw new Error(`Failed to find the \`${t}\` symbol in any files`);let m=i.sort((l,s)=>s.score-l.score).slice(0,r).map(ct).join(` `);return c>i.length&&(m+=` --- Showing ${i.length} matches out of ${c} ---`),m}});function tt(e){e=e.replace(/node_modules\/(\w+)/g,"node_modules/{@types/,}$1");let t=`.{${C.join(",")}}`;if(C.some(c=>e.endsWith(`.${c}`)))return e;if(e==="."||e==="./")return`./**/*${t}`;if(e.endsWith("/"))return`${e.replace(/\/$/,"")}/**/*${t}`;let r=e.includes("*")||e.includes("?")||e.includes("["),o=e.split("/").pop()||"",i=/\.\w+$/.test(o)&&C.includes(o.split(".").pop());return!r&&!i?`${e}/**/*${t}`:e==="*"||e==="*.*"?`*${t}`:`${e}${t}`}function nt(e){let t=Ve.concat(Ze.map(r=>`**/${r}`)).filter(r=>!e.some(o=>o.includes(r))),n=[`!**/{${Ye.join(",")}}`];return t.length&&n.push(`!{${t.join(",")}}/**`),n}async function*rt(e,t){let n=(0,ee.default)(ze),r=!1,o=nt(t),i=[...t,...o],c=Ge.stream(i,{cwd:a.CWD,onlyFiles:!0,absolute:!1,stats:!0,suppressErrors:!0,deep:4}),m=new Set,l=0;try{for await(let s of c){if(++l===Xe||r)break;if(s.stats&&s.stats.size>qe)continue;let p=l,h=n(async()=>{if(r)return[];try{let O=await Ue.readFile(a.resolve(s.path),"utf8");return r?[]:ot(O,e,s.path,p)}catch{return[]}});m.add(h);let ae=await h;m.delete(h);for(let O of ae)yield O}}finally{r=!0,await Promise.allSettled([...m])}}function ot(e,t,n,r){let o=[];if(!e.includes(` `))return o;for(let i of it(e,t)){let m=e.substring(0,i.index).split(` `).length,[l]=i;if(l.length>Je)continue;let s=m+l.split(` `).length-1,p=lt(l,n);o.push({text:l,startLine:m,endLine:s,path:n,score:p,index:i.index,fileIndex:r})}return o}function it(e,t){let n=st(t);return n.lastIndex=0,e.matchAll(n)||[]}var st=Q.memoize(e=>new RegExp(`^(?:\\s*/[/*][^ ]* )*([ ]*).{0,${Ke}}(?<![([.'"])`+at(e)+`(?![.'")]]).*\\s*\\{(?:\r? \\1\\s+.*)+[^}]*\\}`,"mg"));function at(e){let t=Q.escapeRegExp(e);return/^[\w*]/.test(e)&&(t=`\\b${t}`),/[\w*]$/.test(e)&&(t+="\\b"),t.replace(/\\\*/g,"\\w*")}function lt(e,t){let n=0,r=e.split(` `);n+=r.length*2;let o=r[0].match(Qe)||[];n+=o.length*1e3;let i=t.split("/").length;return n-=i*10,t.endsWith(".d.ts")&&(n+=100),Math.round(n)}function ct(e){let t=`${e.startLine}:${e.endLine}:${e.path}`;if(f.CLI&&f.DEBUG){let{length:n}=e.text,r=Math.round(process.uptime()*1e3);t+=` | Chars: ${n} | Index: ${e.index}-${e.index+n} | File: #${e.fileIndex} | Score: ${e.score} | Time: ${r}ms`}return`=== ${t} === ${e.text}`}var te=et;import mt from"lodash";import{z as x}from"zod";var ne=2,re="search_replace",ft=d({id:re,name:`${f.OVERRIDE_S_R?"":"better_"}${re}`,schema:x.object({file_path:x.string().min(1).describe("Path to the file (supports relative and absolute paths)"),old_string:x.string().min(1).describe("Exact text to replace (must be unique in file)"),new_string:x.string().describe("Replacement text"),allow_multiple_matches:x.boolean().optional().describe("Allow multiple matches to be replaced. If false, throws error when multiple matches found (default: true)")}),description:"Search and replace with intelligent whitespace handling and automation-friendly multiple match resolution. Tries exact match first, falls back to flexible whitespace matching only when no matches found.",isReadOnly:!1,isEnabled:f.DEBUG,fromArgs:([e,t,n])=>({file_path:e,old_string:t,new_string:n}),handler:e=>{let{file_path:t,old_string:n,new_string:r,allow_multiple_matches:o=!0}=e,i=a.resolve(t),c=a.readFile(i);if(n.includes(r))throw new Error(`Redundant replacement: old_string already contains new_string. Old: "${n}", New: "${r}"`);let m=[n,pt(n)];for(let l of m){let s=c.split(l),p=s.length-1;if(!p)continue;if(p>1&&!o)throw new Error(`Multiple matches found (${p}) for "${n}" in ${t}. Set allow_multiple_matches=true to allow replacing first occurrence, or make your search string more specific.`);let h=s.join(r);return a.writeFile(i,h),ut(c,h)}throw new Error(`Could not find the specified text in ${t}`)}});function pt(e){return new RegExp(mt.escapeRegExp(e).replace(/\s+/g,"\\s+").replace(/^\s*/,"\\s*").replace(/\s*$/,"\\s*"),"gm")}var oe=ft;function ut(e,t){let n=e.split(` `),r=t.split(` `),o=-1,i=-1;for(let s=0;s<Math.max(n.length,r.length);s++)n[s]!==r[s]&&(o===-1&&(o=s),i=s);if(o===-1)throw new Error("Could not generate a diff for changes to the file");let c=Math.max(0,o-ne),m=Math.min(n.length-1,i+ne),l=[];for(let s=c;s<=m;s++){let p=n[s]||"",h=r[s]||"";s<o||s>i?l.push(` ${p}`):p!==h?(p&&l.push(`- ${p}`),h&&l.push(`+ ${h}`)):l.push(` ${p}`)}return`The following diff was applied to the file: \`\`\` ${l.join(` `)} \`\`\``}import{z as dt}from"zod";var ht=d({id:"utils_debug",schema:dt.object({}),description:a.trimLines(` Get debug information about available tools and environment. - ${u.name} version: ${u.version} `),isReadOnly:!0,isEnabled:f.DEBUG,fromArgs:()=>({}),handler:(e,t)=>({...e,processEnv:process.env,argv:process.argv,env:f,context:t,version:u.version,CWD:a.CWD,REPO:a.REPO})}),ie=ht;function d(e){return{isResource:!1,isReadOnly:!1,isEnabled:!0,name:e.name??e.id,...e}}var gt={read_symbol:te,import_symbol:U,search_replace:oe,insert_text:X,os_notification:J,utils_debug:ie},y=gt;var wt={isCommand:e=>e&&e in y,async run(e){let t=e.shift(),n=y[t];if(!n)throw new Error(`Unknown command: ${t}`);f.CLI=!0;let r=await this.runTool(n,n.fromArgs(e));console.log(r)},async runTool(e,t){try{t=e.schema.parse(t);let n=await e.handler(t);return yt.isString(n)?n:JSON.stringify(n)}catch(n){let r={name:e.name,args:t,error:n.message};throw n instanceof bt&&(r.issues=n.issues.map(o=>`${o.path.join(".")}: ${o.message}`).join(", ")),E.error("Tool execution failed",r),n}}},g=wt;import{FastMCP as $t}from"fastmcp";async function _t(){let e=new $t({name:`${u.author}/${u.name}`,version:u.version});for(let t of Object.values(y))t.isEnabled&&(t.isResource?e.addResource({uri:`resource://${t.name}`,name:t.description,mimeType:"text/plain",load:()=>g.runTool(t,[]).then(n=>({text:n}))}):e.addTool({annotations:{openWorldHint:!1,readOnlyHint:t.isReadOnly,title:t.name},name:t.name,description:t.description,parameters:t.schema,execute:n=>g.runTool(t,n)}));f.TRANSPORT==="http"?await e.start({transportType:"httpStream",httpStream:{port:f.PORT}}):(await e.start({transportType:"stdio"}),E.log("Started new server",{transport:f.TRANSPORT}))}var se={start:_t};var R=process.argv.slice(2);if(R.length===0)await se.start();else if(g.isCommand(R[0]))await g.run(R);else if(R[0]==="--check")process.exit(0);else{let e=u.name;console.log(`${u.author}/${e} ${u.version} ${u.description} Server Usage: ${e} # Run MCP server with stdio transport TRANSPORT=http ${e} # Run MCP server with HTTP transport CLI Usage: ${e} read_symbol <symbol> <file1> [file2...] # Find code blocks by symbol name ${e} import_symbol <module_path> [property] # Inspect modules and imports ${e} search_replace <file> <old_text> <new_text> # Search and replace with whitespace handling ${e} insert_text <file> <from_line> <text> [to_line] # Insert or replace text at line range (1-based) ${e} os_notification <message> [title] # Send OS notifications (title defaults to current directory) ${e} utils_debug # Get debug information Examples: ${e} read_symbol "ToolConfig" src/types.ts ${e} import_symbol lodash get ${e} search_replace src/app.ts "old code" "new code" ${e} insert_text src/app.ts 10 "console.log('debug')" # Insert at line 10 ${e} insert_text src/app.ts 10 "new code" 12 # Replace lines 10-12 ${e} os_notification "Build complete" ${e} utils_debug `),process.exit(0)}export{g as cli,y as tools};