@lmdat/google-sheets-oauth-mcp 1.0.1 → 1.0.2

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 +4 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer as ee}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as te}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as a}from"zod";import{OAuth2Client as V}from"google-auth-library";import w from"node:fs";import A from"node:path";import K from"node:os";import J from"node:http";import{URL as j}from"node:url";import{exec as z}from"node:child_process";var _=process.env.MCP_GSHEETS_TOKEN_DIR||A.join(K.homedir(),".mcp-google-sheets"),T=A.join(_,"token.json"),$=process.env.GOOGLE_OAUTH_CLIENT_ID,v=process.env.GOOGLE_OAUTH_CLIENT_SECRET,O=Number(process.env.GOOGLE_OAUTH_REDIRECT_PORT||53682),b=`http://127.0.0.1:${O}/oauth2callback`,q=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/drive.readonly","https://www.googleapis.com/auth/drive.file"];function W(){if(!$||!v)throw new Error("Thi\u1EBFu GOOGLE_OAUTH_CLIENT_ID ho\u1EB7c GOOGLE_OAUTH_CLIENT_SECRET trong env. L\u1EA5y 2 gi\xE1 tr\u1ECB n\xE0y t\u1EEB OAuth Client (lo\u1EA1i 'Desktop app') trong Google Cloud Console.");return new V({clientId:$,clientSecret:v,redirectUri:b})}function X(e){if(!w.existsSync(T))return!1;let t=JSON.parse(w.readFileSync(T,"utf-8"));return e.setCredentials(t),!0}function L(e){w.mkdirSync(_,{recursive:!0}),w.writeFileSync(T,JSON.stringify(e,null,2),{mode:384})}function Z(e){let t=process.platform,n=t==="darwin"?`open "${e}"`:t==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;z(n,()=>{})}async function Q(e){let t=e.generateAuthUrl({access_type:"offline",scope:q,prompt:"consent"});console.error(`
2
+ import{McpServer as ee}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as te}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as a}from"zod";import{OAuth2Client as V}from"google-auth-library";import I from"node:fs";import A from"node:path";import K from"node:os";import J from"node:http";import{URL as j}from"node:url";import{exec as q}from"node:child_process";var _=process.env.MCP_GSHEETS_TOKEN_DIR||A.join(K.homedir(),".mcp-google-sheets"),T=A.join(_,"token.json"),$=process.env.GOOGLE_OAUTH_CLIENT_ID,v=process.env.GOOGLE_OAUTH_CLIENT_SECRET,b=Number(process.env.GOOGLE_OAUTH_REDIRECT_PORT||53682),O=`http://127.0.0.1:${b}/oauth2callback`,z=["https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/drive.readonly","https://www.googleapis.com/auth/drive.file"];function W(){if(!$||!v)throw new Error("Thi\u1EBFu GOOGLE_OAUTH_CLIENT_ID ho\u1EB7c GOOGLE_OAUTH_CLIENT_SECRET trong env. L\u1EA5y 2 gi\xE1 tr\u1ECB n\xE0y t\u1EEB OAuth Client (lo\u1EA1i 'Desktop app') trong Google Cloud Console.");return new V({clientId:$,clientSecret:v,redirectUri:O})}function X(e){if(!I.existsSync(T))return!1;let t=JSON.parse(I.readFileSync(T,"utf-8"));return e.setCredentials(t),!0}function L(e){I.mkdirSync(_,{recursive:!0}),I.writeFileSync(T,JSON.stringify(e,null,2),{mode:384})}function Z(e){let t=process.platform,n=t==="darwin"?`open "${e}"`:t==="win32"?`start "" "${e}"`:`xdg-open "${e}"`;q(n,()=>{})}async function Q(e){let t=e.generateAuthUrl({access_type:"offline",scope:z,prompt:"consent"});console.error(`
3
3
  [mcp-google-sheets-oauth] Ch\u01B0a c\xF3 token. M\u1EDF URL sau \u0111\u1EC3 \u0111\u0103ng nh\u1EADp (\u0111ang t\u1EF1 m\u1EDF browser...):
4
4
  ${t}
5
- `),Z(t);let n=await new Promise((o,c)=>{let s=J.createServer((d,i)=>{i.setHeader("Content-Type","text/html; charset=utf-8");let h=new j(d.url||"",b),u=h.searchParams.get("code"),I=h.searchParams.get("error");if(I){i.end("\u0110\u0103ng nh\u1EADp b\u1ECB t\u1EEB ch\u1ED1i ho\u1EB7c l\u1ED7i. \u0110\xF3ng tab n\xE0y, xem log \u1EDF terminal."),s.close(),c(new Error(`Google tr\u1EA3 l\u1ED7i OAuth: ${I}`));return}if(u){i.end("<h2>\u0110\u0103ng nh\u1EADp th\xE0nh c\xF4ng!</h2><p>\u0110\xF3ng tab n\xE0y v\xE0 quay l\u1EA1i terminal/opencode.</p>"),s.close(),o(u);return}i.end("Thi\u1EBFu tham s\u1ED1 code/error trong redirect.")});s.listen(O,"127.0.0.1")}),{tokens:r}=await e.getToken(n);e.setCredentials(r),L(r),console.error(`[mcp-google-sheets-oauth] \u0110\xE3 l\u01B0u token v\xE0o: ${T}`)}var y=null;async function Y(){if(y)return y;let e=W();return X(e)||await Q(e),e.on("tokens",n=>{let r={...e.credentials,...n};L(r)}),y=e,e}async function G(){let e=await Y(),{token:t}=await e.getAccessToken();if(!t)throw new Error("Kh\xF4ng l\u1EA5y \u0111\u01B0\u1EE3c access token t\u1EEB OAuth client.");return t}var ne="https://sheets.googleapis.com/v4/spreadsheets",k="https://www.googleapis.com/drive/v3";async function re(e){try{let t=await e.json();if(t?.error?.message){let n=`Google API l\u1ED7i (${e.status}): ${t.error.message}`;return e.status===403&&(n+=`
5
+ `),Z(t);let n=await new Promise((o,c)=>{let s=J.createServer((d,i)=>{i.setHeader("Content-Type","text/html; charset=utf-8");let h=new j(d.url||"",O),u=h.searchParams.get("code"),w=h.searchParams.get("error");if(w){i.end("\u0110\u0103ng nh\u1EADp b\u1ECB t\u1EEB ch\u1ED1i ho\u1EB7c l\u1ED7i. \u0110\xF3ng tab n\xE0y, xem log \u1EDF terminal."),s.close(),c(new Error(`Google tr\u1EA3 l\u1ED7i OAuth: ${w}`));return}if(u){i.end("<h2>\u0110\u0103ng nh\u1EADp th\xE0nh c\xF4ng!</h2><p>\u0110\xF3ng tab n\xE0y v\xE0 quay l\u1EA1i terminal/opencode.</p>"),s.close(),o(u);return}i.end("Thi\u1EBFu tham s\u1ED1 code/error trong redirect.")});s.listen(b,"127.0.0.1")}),{tokens:r}=await e.getToken(n);e.setCredentials(r),L(r),console.error(`[mcp-google-sheets-oauth] \u0110\xE3 l\u01B0u token v\xE0o: ${T}`)}var E=null;async function Y(){if(E)return E;let e=W();return X(e)||await Q(e),e.on("tokens",n=>{let r={...e.credentials,...n};L(r)}),E=e,e}async function G(){let e=await Y(),{token:t}=await e.getAccessToken();if(!t)throw new Error("Kh\xF4ng l\u1EA5y \u0111\u01B0\u1EE3c access token t\u1EEB OAuth client.");return t}var ne="https://sheets.googleapis.com/v4/spreadsheets",k="https://www.googleapis.com/drive/v3";async function re(e){try{let t=await e.json();if(t?.error?.message){let n=`Google API l\u1ED7i (${e.status}): ${t.error.message}`;return e.status===403&&(n+=`
6
6
  \u2192 Kh\u1EA3 n\u0103ng cao: t\xE0i kho\u1EA3n anh ch\u01B0a c\xF3 quy\u1EC1n Edit/Viewer tr\xEAn sheet n\xE0y, ho\u1EB7c scope OAuth \u0111\xE3 xin ch\u01B0a \u0111\u1EE7 (c\u1EA7n re-consent v\u1EDBi scope m\u1EDBi).`),e.status===404&&(n+=`
7
- \u2192 Ki\u1EC3m tra l\u1EA1i spreadsheet_id, c\xF3 th\u1EC3 sai ho\u1EB7c anh kh\xF4ng c\xF3 quy\u1EC1n truy c\u1EADp.`),n}return`Google API l\u1ED7i (${e.status}): ${JSON.stringify(t)}`}catch{return`Google API l\u1ED7i (${e.status}): ${e.statusText}`}}async function C(e,t,n){let r=await G(),o=await fetch(e,{method:t,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:n!==void 0?JSON.stringify(n):void 0});if(!o.ok)throw new Error(await re(o));return o.json()}var l=(e,t,n)=>C(`${ne}${e}`,t,n),oe=a.union([a.string(),a.number(),a.boolean(),a.null()]),N=a.array(a.array(oe));function m(){return a.string().min(1).describe("ID c\u1EE7a Google Sheet \u2014 l\u1EA5y t\u1EEB URL (docs.google.com/spreadsheets/d/<ID>/edit) ho\u1EB7c d\xF9ng tool drive_list_spreadsheets \u0111\u1EC3 t\xECm theo t\xEAn.")}function f(e){return a.string().min(1).describe(`Range theo A1 notation, v\xED d\u1EE5 "${e}". C\xF3 th\u1EC3 ch\u1EC9 ghi t\xEAn sheet (vd "Sheet1") \u0111\u1EC3 l\u1EA5y/ghi c\u1EA3 sheet.`)}function E(e){let t=0;for(let n of e.toUpperCase())t=t*26+(n.charCodeAt(0)-64);return t-1}function se(e){let t="",n=e+1;for(;n>0;){let r=(n-1)%26;t=String.fromCharCode(65+r)+t,n=Math.floor((n-1)/26)}return t}function ae(e){let t=e.match(/^([^!]+)!([A-Za-z]+)(\d+):([A-Za-z]+)(\d+)$/);if(!t)throw new Error(`Range "${e}" kh\xF4ng \u0111\xFAng format. C\u1EA7n d\u1EA1ng "TenSheet!A1:E20" (b\u1EAFt bu\u1ED9c c\xF3 t\xEAn sheet, \u0111\u1EE7 2 g\xF3c range).`);let[,n,r,o,c,s]=t;return{sheetName:n,startRowIndex:parseInt(o,10)-1,endRowIndex:parseInt(s,10),startColumnIndex:E(r),endColumnIndex:E(c)+1}}function ie(e){let t=e.match(/^([^!]+)!([A-Za-z]+)(\d+)$/);if(!t)throw new Error(`Cell "${e}" kh\xF4ng \u0111\xFAng format. C\u1EA7n d\u1EA1ng "TenSheet!F2".`);let[,n,r,o]=t;return{sheetName:n,rowIndex:parseInt(o,10)-1,columnIndex:E(r)}}async function P(e,t,n){let r=n.get(t);if(r!==void 0)return r;let c=((await l(`/${e}?fields=sheets.properties`,"GET")).sheets||[]).find(s=>s.properties.title===t);if(!c)throw new Error(`Kh\xF4ng t\xECm th\u1EA5y tab "${t}" trong spreadsheet. D\xF9ng tool sheets_list_tabs \u0111\u1EC3 xem t\xEAn tab \u0111\xFAng.`);return n.set(t,c.properties.sheetId),c.properties.sheetId}async function x(e,t,n){let r=ae(t),o=await P(e,r.sheetName,n);return{sheetName:r.sheetName,sheetId:o,gridRange:{sheetId:o,startRowIndex:r.startRowIndex,endRowIndex:r.endRowIndex,startColumnIndex:r.startColumnIndex,endColumnIndex:r.endColumnIndex}}}async function U(e,t,n){let r=ie(t);return{sheetId:await P(e,r.sheetName,n),rowIndex:r.rowIndex,columnIndex:r.columnIndex}}async function ce(e,t,n){let r=se(n.startColumnIndex),o=n.startRowIndex+1,c=n.endRowIndex,s=`${t}!${r}${o}:${r}${c}`,i=(await l(`/${e}/values/${encodeURIComponent(s)}?valueRenderOption=FORMATTED_VALUE`,"GET")).values||[];i.length!==0&&await l(`/${e}/values/${encodeURIComponent(s)}?valueInputOption=RAW`,"PUT",{values:i})}async function D(e,t,n){let r={requests:[{addChart:{chart:{spec:t,position:{overlayPosition:{anchorCell:{sheetId:n.sheetId,rowIndex:n.rowIndex,columnIndex:n.columnIndex},widthPixels:600,heightPixels:371}}}}}]},c=(await l(`/${e}:batchUpdate`,"POST",r)).replies?.[0]?.addChart?.chart?.chartId;if(c===void 0)throw new Error("T\u1EA1o chart kh\xF4ng tr\u1EA3 v\u1EC1 chartId \u2014 ki\u1EC3m tra l\u1EA1i response Google API.");return c}var g=new ee({name:"google-sheets-oauth",version:"1.0.0"});g.registerTool("drive_list_spreadsheets",{title:"T\xECm Google Sheet theo t\xEAn",description:"List c\xE1c Google Sheet trong Drive c\u1EE7a ch\xEDnh user \u0111ang login (kh\xF4ng c\u1EA7n share th\u1EE7 c\xF4ng, v\xEC OAuth \u0111\u1EA1i di\u1EC7n ch\xEDnh user \u0111\xF3). D\xF9ng \u0111\u1EC3 t\xECm spreadsheet_id theo t\xEAn thay v\xEC copy URL. Tr\u1EA3 v\u1EC1 JSON array, m\u1ED7i item: {name, id, url, modifiedTime, owner}.",inputSchema:{name_contains:a.string().optional().describe('L\u1ECDc theo t\xEAn file ch\u1EE9a chu\u1ED7i n\xE0y (vd "Ng\xE2n s\xE1ch"). B\u1ECF tr\u1ED1ng \u0111\u1EC3 l\u1EA5y g\u1EA7n \u0111\xE2y nh\u1EA5t.'),max_results:a.number().int().min(1).max(50).default(10)}},async({name_contains:e,max_results:t})=>{try{let n=["mimeType='application/vnd.google-apps.spreadsheet'","trashed=false"];if(e){let i=e.replace(/'/g,"\\'");n.push(`name contains '${i}'`)}let r=encodeURIComponent(n.join(" and ")),o=encodeURIComponent("files(id,name,modifiedTime,webViewLink,owners(displayName))"),d=((await C(`${k}/files?q=${r}&fields=${o}&orderBy=modifiedTime desc&pageSize=${t}`,"GET")).files||[]).map(i=>({name:i.name,id:i.id,url:i.webViewLink,modifiedTime:i.modifiedTime,owner:i.owners?.[0]?.displayName??null}));return{content:[{type:"text",text:JSON.stringify(d,null,2)}]}}catch(n){return{content:[{type:"text",text:`L\u1ED7i: ${n.message}`}],isError:!0}}});g.registerTool("sheets_create",{title:"T\u1EA1o Google Sheet m\u1EDBi",description:"T\u1EA1o 1 spreadsheet m\u1EDBi (r\u1ED7ng). M\u1EB7c \u0111\u1ECBnh n\u1EB1m \u1EDF root My Drive c\u1EE7a account \u0111ang login. Truy\u1EC1n folder_id n\u1EBFu mu\u1ED1n \u0111\u1EB7t lu\xF4n v\xE0o 1 folder c\u1EE5 th\u1EC3.",inputSchema:{title:a.string().min(1).describe("T\xEAn file Google Sheet m\u1EDBi."),folder_id:a.string().optional().describe("ID folder Drive mu\u1ED1n \u0111\u1EB7t file v\xE0o (l\u1EA5y t\u1EEB URL folder tr\xEAn Drive). B\u1ECF tr\u1ED1ng -> file n\u1EB1m \u1EDF root My Drive."),sheet_titles:a.array(a.string()).optional().describe('T\xEAn c\xE1c tab mu\u1ED1n t\u1EA1o s\u1EB5n, v\xED d\u1EE5 ["Thu","Chi"]. B\u1ECF tr\u1ED1ng -> 1 tab m\u1EB7c \u0111\u1ECBnh "Sheet1".')}},async({title:e,folder_id:t,sheet_titles:n})=>{try{let r={properties:{title:e}};n&&n.length>0&&(r.sheets=n.map(d=>({properties:{title:d}})));let o=await l("","POST",r),c=o.spreadsheetId,s=o.spreadsheetUrl;return t&&await C(`${k}/files/${c}?addParents=${t}&removeParents=root&fields=id,parents`,"PATCH"),{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o sheet "${e}".
7
+ \u2192 Ki\u1EC3m tra l\u1EA1i spreadsheet_id, c\xF3 th\u1EC3 sai ho\u1EB7c anh kh\xF4ng c\xF3 quy\u1EC1n truy c\u1EADp.`),n}return`Google API l\u1ED7i (${e.status}): ${JSON.stringify(t)}`}catch{return`Google API l\u1ED7i (${e.status}): ${e.statusText}`}}async function C(e,t,n){let r=await G(),o=await fetch(e,{method:t,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},body:n!==void 0?JSON.stringify(n):void 0});if(!o.ok)throw new Error(await re(o));return o.json()}var l=(e,t,n)=>C(`${ne}${e}`,t,n),oe=a.union([a.string(),a.number(),a.boolean(),a.null()]),N=a.array(a.array(oe));function m(){return a.string().min(1).describe("ID c\u1EE7a Google Sheet \u2014 l\u1EA5y t\u1EEB URL (docs.google.com/spreadsheets/d/<ID>/edit) ho\u1EB7c d\xF9ng tool drive_list_spreadsheets \u0111\u1EC3 t\xECm theo t\xEAn.")}function f(e){return a.string().min(1).describe(`Range theo A1 notation, v\xED d\u1EE5 "${e}". C\xF3 th\u1EC3 ch\u1EC9 ghi t\xEAn sheet (vd "Sheet1") \u0111\u1EC3 l\u1EA5y/ghi c\u1EA3 sheet.`)}function R(e){let t=0;for(let n of e.toUpperCase())t=t*26+(n.charCodeAt(0)-64);return t-1}function se(e){let t="",n=e+1;for(;n>0;){let r=(n-1)%26;t=String.fromCharCode(65+r)+t,n=Math.floor((n-1)/26)}return t}function ae(e){let t=e.match(/^([^!]+)!([A-Za-z]+)(\d+):([A-Za-z]+)(\d+)$/);if(!t)throw new Error(`Range "${e}" kh\xF4ng \u0111\xFAng format. C\u1EA7n d\u1EA1ng "TenSheet!A1:E20" (b\u1EAFt bu\u1ED9c c\xF3 t\xEAn sheet, \u0111\u1EE7 2 g\xF3c range).`);let[,n,r,o,c,s]=t;return{sheetName:n,startRowIndex:parseInt(o,10)-1,endRowIndex:parseInt(s,10),startColumnIndex:R(r),endColumnIndex:R(c)+1}}function ie(e){let t=e.match(/^([^!]+)!([A-Za-z]+)(\d+)$/);if(!t)throw new Error(`Cell "${e}" kh\xF4ng \u0111\xFAng format. C\u1EA7n d\u1EA1ng "TenSheet!F2".`);let[,n,r,o]=t;return{sheetName:n,rowIndex:parseInt(o,10)-1,columnIndex:R(r)}}async function P(e,t,n){let r=n.get(t);if(r!==void 0)return r;let c=((await l(`/${e}?fields=sheets.properties`,"GET")).sheets||[]).find(s=>s.properties.title===t);if(!c)throw new Error(`Kh\xF4ng t\xECm th\u1EA5y tab "${t}" trong spreadsheet. D\xF9ng tool sheets_list_tabs \u0111\u1EC3 xem t\xEAn tab \u0111\xFAng.`);return n.set(t,c.properties.sheetId),c.properties.sheetId}async function x(e,t,n){let r=ae(t),o=await P(e,r.sheetName,n);return{sheetName:r.sheetName,sheetId:o,gridRange:{sheetId:o,startRowIndex:r.startRowIndex,endRowIndex:r.endRowIndex,startColumnIndex:r.startColumnIndex,endColumnIndex:r.endColumnIndex}}}async function U(e,t,n){let r=ie(t);return{sheetId:await P(e,r.sheetName,n),rowIndex:r.rowIndex,columnIndex:r.columnIndex}}async function ce(e,t,n){let r=se(n.startColumnIndex),o=n.startRowIndex+1,c=n.endRowIndex,s=`${t}!${r}${o}:${r}${c}`,i=(await l(`/${e}/values/${encodeURIComponent(s)}?valueRenderOption=FORMATTED_VALUE`,"GET")).values||[];i.length!==0&&(await l(`/${e}/values/${encodeURIComponent(s)}?valueInputOption=RAW`,"PUT",{values:i}),await l(`/${e}:batchUpdate`,"POST",{requests:[{repeatCell:{range:n,cell:{userEnteredFormat:{numberFormat:{type:"TEXT"}}},fields:"userEnteredFormat.numberFormat"}}]}))}async function D(e,t,n){let r={requests:[{addChart:{chart:{spec:t,position:{overlayPosition:{anchorCell:{sheetId:n.sheetId,rowIndex:n.rowIndex,columnIndex:n.columnIndex},widthPixels:600,heightPixels:371}}}}}]},c=(await l(`/${e}:batchUpdate`,"POST",r)).replies?.[0]?.addChart?.chart?.chartId;if(c===void 0)throw new Error("T\u1EA1o chart kh\xF4ng tr\u1EA3 v\u1EC1 chartId \u2014 ki\u1EC3m tra l\u1EA1i response Google API.");return c}var g=new ee({name:"google-sheets-oauth",version:"1.0.0"});g.registerTool("drive_list_spreadsheets",{title:"T\xECm Google Sheet theo t\xEAn",description:"List c\xE1c Google Sheet trong Drive c\u1EE7a ch\xEDnh user \u0111ang login (kh\xF4ng c\u1EA7n share th\u1EE7 c\xF4ng, v\xEC OAuth \u0111\u1EA1i di\u1EC7n ch\xEDnh user \u0111\xF3). D\xF9ng \u0111\u1EC3 t\xECm spreadsheet_id theo t\xEAn thay v\xEC copy URL. Tr\u1EA3 v\u1EC1 JSON array, m\u1ED7i item: {name, id, url, modifiedTime, owner}.",inputSchema:{name_contains:a.string().optional().describe('L\u1ECDc theo t\xEAn file ch\u1EE9a chu\u1ED7i n\xE0y (vd "Ng\xE2n s\xE1ch"). B\u1ECF tr\u1ED1ng \u0111\u1EC3 l\u1EA5y g\u1EA7n \u0111\xE2y nh\u1EA5t.'),max_results:a.number().int().min(1).max(50).default(10)}},async({name_contains:e,max_results:t})=>{try{let n=["mimeType='application/vnd.google-apps.spreadsheet'","trashed=false"];if(e){let i=e.replace(/'/g,"\\'");n.push(`name contains '${i}'`)}let r=encodeURIComponent(n.join(" and ")),o=encodeURIComponent("files(id,name,modifiedTime,webViewLink,owners(displayName))"),d=((await C(`${k}/files?q=${r}&fields=${o}&orderBy=modifiedTime desc&pageSize=${t}`,"GET")).files||[]).map(i=>({name:i.name,id:i.id,url:i.webViewLink,modifiedTime:i.modifiedTime,owner:i.owners?.[0]?.displayName??null}));return{content:[{type:"text",text:JSON.stringify(d,null,2)}]}}catch(n){return{content:[{type:"text",text:`L\u1ED7i: ${n.message}`}],isError:!0}}});g.registerTool("sheets_create",{title:"T\u1EA1o Google Sheet m\u1EDBi",description:"T\u1EA1o 1 spreadsheet m\u1EDBi (r\u1ED7ng). M\u1EB7c \u0111\u1ECBnh n\u1EB1m \u1EDF root My Drive c\u1EE7a account \u0111ang login. Truy\u1EC1n folder_id n\u1EBFu mu\u1ED1n \u0111\u1EB7t lu\xF4n v\xE0o 1 folder c\u1EE5 th\u1EC3.",inputSchema:{title:a.string().min(1).describe("T\xEAn file Google Sheet m\u1EDBi."),folder_id:a.string().optional().describe("ID folder Drive mu\u1ED1n \u0111\u1EB7t file v\xE0o (l\u1EA5y t\u1EEB URL folder tr\xEAn Drive). B\u1ECF tr\u1ED1ng -> file n\u1EB1m \u1EDF root My Drive."),sheet_titles:a.array(a.string()).optional().describe('T\xEAn c\xE1c tab mu\u1ED1n t\u1EA1o s\u1EB5n, v\xED d\u1EE5 ["Thu","Chi"]. B\u1ECF tr\u1ED1ng -> 1 tab m\u1EB7c \u0111\u1ECBnh "Sheet1".')}},async({title:e,folder_id:t,sheet_titles:n})=>{try{let r={properties:{title:e}};n&&n.length>0&&(r.sheets=n.map(d=>({properties:{title:d}})));let o=await l("","POST",r),c=o.spreadsheetId,s=o.spreadsheetUrl;return t&&await C(`${k}/files/${c}?addParents=${t}&removeParents=root&fields=id,parents`,"PATCH"),{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o sheet "${e}".
8
8
  spreadsheet_id: ${c}
9
9
  URL: ${s}${t?`
10
10
  \u0110\xE3 move v\xE0o folder: ${t}`:""}`}]}}catch(r){return{content:[{type:"text",text:`L\u1ED7i: ${r.message}`}],isError:!0}}});g.registerTool("sheets_read",{title:"\u0110\u1ECDc d\u1EEF li\u1EC7u t\u1EEB Google Sheet",description:"\u0110\u1ECDc gi\xE1 tr\u1ECB 1 v\xF9ng (range) trong Google Sheet, tr\u1EA3 v\u1EC1 d\u1EA1ng m\u1EA3ng 2 chi\u1EC1u (h\xE0ng x c\u1ED9t).",inputSchema:{spreadsheet_id:m(),range:f("Sheet1!A1:D10")}},async({spreadsheet_id:e,range:t})=>{try{let r=(await l(`/${e}/values/${encodeURIComponent(t)}`,"GET")).values||[];return{content:[{type:"text",text:r.length===0?"Range tr\u1ED1ng, kh\xF4ng c\xF3 d\u1EEF li\u1EC7u.":JSON.stringify(r,null,2)}]}}catch(n){return{content:[{type:"text",text:`L\u1ED7i: ${n.message}`}],isError:!0}}});g.registerTool("sheets_write",{title:"Ghi \u0111\xE8 d\u1EEF li\u1EC7u v\xE0o Google Sheet",description:"Ghi \u0111\xE8 gi\xE1 tr\u1ECB v\xE0o 1 range c\u1EE5 th\u1EC3. D\u1EEF li\u1EC7u c\u0169 trong range s\u1EBD b\u1ECB thay th\u1EBF ho\xE0n to\xE0n.",inputSchema:{spreadsheet_id:m(),range:f("Sheet1!A1:C3"),values:N.describe('M\u1EA3ng 2 chi\u1EC1u, m\u1ED7i m\u1EA3ng con l\xE0 1 h\xE0ng. V\xED d\u1EE5: [["T\xEAn","Tu\u1ED5i"],["\u0110\u1EA1t",30]]'),value_input_option:a.enum(["RAW","USER_ENTERED"]).default("USER_ENTERED").describe("USER_ENTERED: Sheet t\u1EF1 parse nh\u01B0 khi anh g\xF5 tay (c\xF4ng th\u1EE9c, ng\xE0y th\xE1ng...). RAW: gi\u1EEF nguy\xEAn string.")}},async({spreadsheet_id:e,range:t,values:n,value_input_option:r})=>{try{let o=await l(`/${e}/values/${encodeURIComponent(t)}?valueInputOption=${r}`,"PUT",{values:n});return{content:[{type:"text",text:`\u0110\xE3 ghi ${o.updatedCells??"?"} cell v\xE0o range ${o.updatedRange}.`}]}}catch(o){return{content:[{type:"text",text:`L\u1ED7i: ${o.message}`}],isError:!0}}});g.registerTool("sheets_append",{title:"Append h\xE0ng m\u1EDBi v\xE0o Google Sheet",description:"Th\xEAm h\xE0ng m\u1EDBi v\xE0o cu\u1ED1i b\u1EA3ng d\u1EEF li\u1EC7u hi\u1EC7n c\xF3 (kh\xF4ng \u0111\xE8 d\u1EEF li\u1EC7u c\u0169).",inputSchema:{spreadsheet_id:m(),range:f("Sheet1 (ch\u1EC9 c\u1EA7n t\xEAn sheet)"),values:N.describe("M\u1EA3ng 2 chi\u1EC1u, m\u1ED7i m\u1EA3ng con l\xE0 1 h\xE0ng c\u1EA7n th\xEAm."),value_input_option:a.enum(["RAW","USER_ENTERED"]).default("USER_ENTERED")}},async({spreadsheet_id:e,range:t,values:n,value_input_option:r})=>{try{let c=(await l(`/${e}/values/${encodeURIComponent(t)}:append?valueInputOption=${r}&insertDataOption=INSERT_ROWS`,"POST",{values:n})).updates;return{content:[{type:"text",text:`\u0110\xE3 append ${c?.updatedRows??n.length} h\xE0ng v\xE0o ${c?.updatedRange??t}.`}]}}catch(o){return{content:[{type:"text",text:`L\u1ED7i: ${o.message}`}],isError:!0}}});g.registerTool("sheets_clear",{title:"X\xF3a gi\xE1 tr\u1ECB trong 1 range",description:"X\xF3a n\u1ED9i dung (gi\xE1 tr\u1ECB) trong 1 range, kh\xF4ng x\xF3a format/border.",inputSchema:{spreadsheet_id:m(),range:f("Sheet1!A2:D100")}},async({spreadsheet_id:e,range:t})=>{try{return await l(`/${e}/values/${encodeURIComponent(t)}:clear`,"POST",{}),{content:[{type:"text",text:`\u0110\xE3 x\xF3a d\u1EEF li\u1EC7u trong range ${t}.`}]}}catch(n){return{content:[{type:"text",text:`L\u1ED7i: ${n.message}`}],isError:!0}}});g.registerTool("sheets_list_tabs",{title:"List c\xE1c tab/sheet trong file",description:"Li\u1EC7t k\xEA t\xEAn + ID c\xE1c tab (sheet con) trong 1 Google Sheet file.",inputSchema:{spreadsheet_id:m()}},async({spreadsheet_id:e})=>{try{let t=await l(`/${e}?fields=properties.title,sheets.properties`,"GET"),n=(t.sheets||[]).map(r=>({title:r.properties.title,sheetId:r.properties.sheetId,rowCount:r.properties.gridProperties?.rowCount,columnCount:r.properties.gridProperties?.columnCount}));return{content:[{type:"text",text:`File: ${t.properties?.title}
11
11
 
12
12
  Tabs:
13
- ${JSON.stringify(n,null,2)}`}]}}catch(t){return{content:[{type:"text",text:`L\u1ED7i: ${t.message}`}],isError:!0}}});g.registerTool("sheets_create_chart",{title:"T\u1EA1o chart c\u01A1 b\u1EA3n trong Google Sheet",description:'T\u1EA1o 1 chart COLUMN/BAR/LINE/AREA/SCATTER/PIE t\u1EEB data c\xF3 s\u1EB5n, chart \u0111\u01B0\u1EE3c embed th\u1EB3ng v\xE0o tab. M\u1ECDi range PH\u1EA2I ghi \u0111\u1EE7 t\xEAn sheet, d\u1EA1ng "TenSheet!A2:A10" \u2014 kh\xF4ng h\u1ED7 tr\u1EE3 range thi\u1EBFu t\xEAn sheet.',inputSchema:{spreadsheet_id:m(),chart_type:a.enum(["COLUMN","BAR","LINE","AREA","SCATTER","PIE"]),title:a.string().min(1).describe("Ti\xEAu \u0111\u1EC1 chart."),domain_range:a.string().describe('Range nh\xE3n tr\u1EE5c X (category/labels), d\u1EA1ng "TenSheet!A2:A10". V\u1EDBi PIE \u0111\xE2y l\xE0 nh\xE3n t\u1EEBng ph\u1EA7n.'),series_ranges:a.array(a.string()).min(1).describe('Range gi\xE1 tr\u1ECB, m\u1ED7i string l\xE0 1 series, d\u1EA1ng "TenSheet!B2:B10". PIE ch\u1EC9 nh\u1EADn \u0111\xFAng 1 series.'),stacked:a.boolean().default(!1).describe("Ch\u1EC9 c\xF3 \xFD ngh\u0129a v\u1EDBi COLUMN/BAR/AREA. true = stacked chart."),anchor_cell:a.string().optional().describe('V\u1ECB tr\xED g\xF3c tr\xEAn-tr\xE1i \u0111\u1EB7t chart, d\u1EA1ng "TenSheet!F2". B\u1ECF tr\u1ED1ng -> t\u1EF1 \u0111\u1EB7t ngay b\xEAn ph\u1EA3i domain_range.')}},async({spreadsheet_id:e,chart_type:t,title:n,domain_range:r,series_ranges:o,stacked:c,anchor_cell:s})=>{try{if(t==="PIE"&&o.length!==1)throw new Error("PIE chart ch\u1EC9 nh\u1EADn \u0111\xFAng 1 series_ranges, \u0111ang truy\u1EC1n "+o.length+".");let d=new Map,i=await x(e,r,d),h=await Promise.all(o.map(p=>x(e,p,d))),u;t==="PIE"?u={title:n,pieChart:{legendPosition:"BOTTOM_LEGEND",domain:{sourceRange:{sources:[i.gridRange]}},series:{sourceRange:{sources:[h[0].gridRange]}}}}:u={title:n,basicChart:{chartType:t,legendPosition:"BOTTOM_LEGEND",domains:[{domain:{sourceRange:{sources:[i.gridRange]}}}],series:h.map(p=>({series:{sourceRange:{sources:[p.gridRange]}}})),...["COLUMN","BAR","AREA"].includes(t)&&c?{stackedType:"STACKED"}:{}}};let I=s?await U(e,s,d):{sheetId:i.sheetId,rowIndex:i.gridRange.startRowIndex,columnIndex:Math.max(i.gridRange.endColumnIndex,...h.map(p=>p.gridRange.endColumnIndex))+1},R=await D(e,u,I);return{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o ${t} chart "${n}" (chartId: ${R}) trong tab "${i.sheetName}".`}]}}catch(d){return{content:[{type:"text",text:`L\u1ED7i: ${d.message}`}],isError:!0}}});g.registerTool("sheets_create_candlestick_chart",{title:"T\u1EA1o candlestick chart (n\u1EBFn) trong Google Sheet",description:'T\u1EA1o chart n\u1EBFn cho d\u1EEF li\u1EC7u gi\xE1 (OHLC). data_range PH\u1EA2I c\xF3 \u0110\xDANG 5 c\u1ED9t li\xEAn ti\u1EBFp theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh: Date, Open, High, Low, Close \u2014 \u0111\xFAng convention chu\u1EA9n c\u1EE7a Google Sheets (gi\u1ED1ng khi t\u1EF1 ch\u1ECDn data tr\xEAn Insert > Chart b\u1EB1ng tay). V\xED d\u1EE5: "Data!A2:E50" (kh\xF4ng g\u1ED3m d\xF2ng header).',inputSchema:{spreadsheet_id:m(),title:a.string().min(1).describe("Ti\xEAu \u0111\u1EC1 chart."),data_range:a.string().describe('Range 5 c\u1ED9t li\xEAn ti\u1EBFp, th\u1EE9 t\u1EF1 C\u1ED0 \u0110\u1ECANH Date-Open-High-Low-Close, d\u1EA1ng "TenSheet!A2:E50". Kh\xF4ng g\u1ED3m d\xF2ng header.'),anchor_cell:a.string().optional().describe('V\u1ECB tr\xED \u0111\u1EB7t chart, d\u1EA1ng "TenSheet!G2". B\u1ECF tr\u1ED1ng -> t\u1EF1 \u0111\u1EB7t b\xEAn ph\u1EA3i data_range.')}},async({spreadsheet_id:e,title:t,data_range:n,anchor_cell:r})=>{try{let o=new Map,c=await x(e,n,o),{gridRange:s,sheetName:d}=c,i=s.endColumnIndex-s.startColumnIndex;if(i!==5)throw new Error(`data_range ph\u1EA3i c\xF3 \u0110\xDANG 5 c\u1ED9t theo th\u1EE9 t\u1EF1 Date, Open, High, Low, Close \u2014 hi\u1EC7n t\u1EA1i \u0111ang c\xF3 ${i} c\u1ED9t. Ki\u1EC3m tra l\u1EA1i range "${n}".`);let h=S=>({sheetId:s.sheetId,startRowIndex:s.startRowIndex,endRowIndex:s.endRowIndex,startColumnIndex:s.startColumnIndex+S,endColumnIndex:s.startColumnIndex+S+1}),u=h(0),I=h(1),R=h(2),p=h(3),M=h(4);await ce(e,d,u);let B={title:t,candlestickChart:{domain:{data:{sourceRange:{sources:[u]}}},data:[{lowSeries:{data:{sourceRange:{sources:[p]}}},openSeries:{data:{sourceRange:{sources:[I]}}},closeSeries:{data:{sourceRange:{sources:[M]}}},highSeries:{data:{sourceRange:{sources:[R]}}}}]}},H=r?await U(e,r,o):{sheetId:s.sheetId,rowIndex:s.startRowIndex,columnIndex:s.endColumnIndex+1},F=await D(e,B,H);return{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o candlestick chart "${t}" (chartId: ${F}) trong tab "${d}".`}]}}catch(o){return{content:[{type:"text",text:`L\u1ED7i: ${o.message}`}],isError:!0}}});async function de(){let e=new te;await g.connect(e),console.error("[mcp-google-sheets-oauth] Server \u0111ang ch\u1EA1y (stdio).")}de().catch(e=>{console.error("[mcp-google-sheets-oauth] Fatal error:",e),process.exit(1)});
13
+ ${JSON.stringify(n,null,2)}`}]}}catch(t){return{content:[{type:"text",text:`L\u1ED7i: ${t.message}`}],isError:!0}}});g.registerTool("sheets_create_chart",{title:"T\u1EA1o chart c\u01A1 b\u1EA3n trong Google Sheet",description:'T\u1EA1o 1 chart COLUMN/BAR/LINE/AREA/SCATTER/PIE t\u1EEB data c\xF3 s\u1EB5n, chart \u0111\u01B0\u1EE3c embed th\u1EB3ng v\xE0o tab. M\u1ECDi range PH\u1EA2I ghi \u0111\u1EE7 t\xEAn sheet, d\u1EA1ng "TenSheet!A2:A10" \u2014 kh\xF4ng h\u1ED7 tr\u1EE3 range thi\u1EBFu t\xEAn sheet.',inputSchema:{spreadsheet_id:m(),chart_type:a.enum(["COLUMN","BAR","LINE","AREA","SCATTER","PIE"]),title:a.string().min(1).describe("Ti\xEAu \u0111\u1EC1 chart."),domain_range:a.string().describe('Range nh\xE3n tr\u1EE5c X (category/labels), d\u1EA1ng "TenSheet!A2:A10". V\u1EDBi PIE \u0111\xE2y l\xE0 nh\xE3n t\u1EEBng ph\u1EA7n.'),series_ranges:a.array(a.string()).min(1).describe('Range gi\xE1 tr\u1ECB, m\u1ED7i string l\xE0 1 series, d\u1EA1ng "TenSheet!B2:B10". PIE ch\u1EC9 nh\u1EADn \u0111\xFAng 1 series.'),stacked:a.boolean().default(!1).describe("Ch\u1EC9 c\xF3 \xFD ngh\u0129a v\u1EDBi COLUMN/BAR/AREA. true = stacked chart."),anchor_cell:a.string().optional().describe('V\u1ECB tr\xED g\xF3c tr\xEAn-tr\xE1i \u0111\u1EB7t chart, d\u1EA1ng "TenSheet!F2". B\u1ECF tr\u1ED1ng -> t\u1EF1 \u0111\u1EB7t ngay b\xEAn ph\u1EA3i domain_range.')}},async({spreadsheet_id:e,chart_type:t,title:n,domain_range:r,series_ranges:o,stacked:c,anchor_cell:s})=>{try{if(t==="PIE"&&o.length!==1)throw new Error("PIE chart ch\u1EC9 nh\u1EADn \u0111\xFAng 1 series_ranges, \u0111ang truy\u1EC1n "+o.length+".");let d=new Map,i=await x(e,r,d),h=await Promise.all(o.map(p=>x(e,p,d))),u;t==="PIE"?u={title:n,pieChart:{legendPosition:"BOTTOM_LEGEND",domain:{sourceRange:{sources:[i.gridRange]}},series:{sourceRange:{sources:[h[0].gridRange]}}}}:u={title:n,basicChart:{chartType:t,legendPosition:"BOTTOM_LEGEND",domains:[{domain:{sourceRange:{sources:[i.gridRange]}}}],series:h.map(p=>({series:{sourceRange:{sources:[p.gridRange]}}})),...["COLUMN","BAR","AREA"].includes(t)&&c?{stackedType:"STACKED"}:{}}};let w=s?await U(e,s,d):{sheetId:i.sheetId,rowIndex:i.gridRange.startRowIndex,columnIndex:Math.max(i.gridRange.endColumnIndex,...h.map(p=>p.gridRange.endColumnIndex))+1},y=await D(e,u,w);return{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o ${t} chart "${n}" (chartId: ${y}) trong tab "${i.sheetName}".`}]}}catch(d){return{content:[{type:"text",text:`L\u1ED7i: ${d.message}`}],isError:!0}}});g.registerTool("sheets_create_candlestick_chart",{title:"T\u1EA1o candlestick chart (n\u1EBFn) trong Google Sheet",description:'T\u1EA1o chart n\u1EBFn cho d\u1EEF li\u1EC7u gi\xE1 (OHLC). data_range PH\u1EA2I c\xF3 \u0110\xDANG 5 c\u1ED9t li\xEAn ti\u1EBFp theo th\u1EE9 t\u1EF1 c\u1ED1 \u0111\u1ECBnh: Date, Open, High, Low, Close \u2014 \u0111\xFAng convention chu\u1EA9n c\u1EE7a Google Sheets (gi\u1ED1ng khi t\u1EF1 ch\u1ECDn data tr\xEAn Insert > Chart b\u1EB1ng tay). V\xED d\u1EE5: "Data!A2:E50" (kh\xF4ng g\u1ED3m d\xF2ng header).',inputSchema:{spreadsheet_id:m(),title:a.string().min(1).describe("Ti\xEAu \u0111\u1EC1 chart."),data_range:a.string().describe('Range 5 c\u1ED9t li\xEAn ti\u1EBFp, th\u1EE9 t\u1EF1 C\u1ED0 \u0110\u1ECANH Date-Open-High-Low-Close, d\u1EA1ng "TenSheet!A2:E50". Kh\xF4ng g\u1ED3m d\xF2ng header.'),anchor_cell:a.string().optional().describe('V\u1ECB tr\xED \u0111\u1EB7t chart, d\u1EA1ng "TenSheet!G2". B\u1ECF tr\u1ED1ng -> t\u1EF1 \u0111\u1EB7t b\xEAn ph\u1EA3i data_range.')}},async({spreadsheet_id:e,title:t,data_range:n,anchor_cell:r})=>{try{let o=new Map,c=await x(e,n,o),{gridRange:s,sheetName:d}=c,i=s.endColumnIndex-s.startColumnIndex;if(i!==5)throw new Error(`data_range ph\u1EA3i c\xF3 \u0110\xDANG 5 c\u1ED9t theo th\u1EE9 t\u1EF1 Date, Open, High, Low, Close \u2014 hi\u1EC7n t\u1EA1i \u0111ang c\xF3 ${i} c\u1ED9t. Ki\u1EC3m tra l\u1EA1i range "${n}".`);let h=S=>({sheetId:s.sheetId,startRowIndex:s.startRowIndex,endRowIndex:s.endRowIndex,startColumnIndex:s.startColumnIndex+S,endColumnIndex:s.startColumnIndex+S+1}),u=h(0),w=h(1),y=h(2),p=h(3),M=h(4);await ce(e,d,u);let B={title:t,candlestickChart:{domain:{data:{sourceRange:{sources:[u]}}},data:[{lowSeries:{data:{sourceRange:{sources:[p]}}},openSeries:{data:{sourceRange:{sources:[w]}}},closeSeries:{data:{sourceRange:{sources:[M]}}},highSeries:{data:{sourceRange:{sources:[y]}}}}]}},H=r?await U(e,r,o):{sheetId:s.sheetId,rowIndex:s.startRowIndex,columnIndex:s.endColumnIndex+1},F=await D(e,B,H);return{content:[{type:"text",text:`\u0110\xE3 t\u1EA1o candlestick chart "${t}" (chartId: ${F}) trong tab "${d}".`}]}}catch(o){return{content:[{type:"text",text:`L\u1ED7i: ${o.message}`}],isError:!0}}});async function de(){let e=new te;await g.connect(e),console.error("[mcp-google-sheets-oauth] Server \u0111ang ch\u1EA1y (stdio).")}de().catch(e=>{console.error("[mcp-google-sheets-oauth] Fatal error:",e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lmdat/google-sheets-oauth-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Local MCP server đọc/ghi/tạo Google Sheets qua OAuth (đại diện chính user, không cần share thủ công).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",