Skip to main content
Glama

Excel MCP Server

MIT License
1,642
537
  • Linux
  • Apple
excel_ole.go24.9 kB
package excel import ( "bufio" "bytes" "encoding/base64" "fmt" "io" "path/filepath" "regexp" "runtime" "strings" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" "github.com/skanehira/clipboard-image" ) type OleExcel struct { application *ole.IDispatch workbook *ole.IDispatch } type OleWorksheet struct { excel *OleExcel worksheet *ole.IDispatch } func NewExcelOle(absolutePath string) (*OleExcel, func(), error) { runtime.LockOSThread() ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED) unknown, err := oleutil.GetActiveObject("Excel.Application") if err != nil { return nil, func() {}, err } excel, err := unknown.QueryInterface(ole.IID_IDispatch) if err != nil { return nil, func() {}, err } oleutil.MustPutProperty(excel, "ScreenUpdating", false) oleutil.MustPutProperty(excel, "EnableEvents", false) workbooks := oleutil.MustGetProperty(excel, "Workbooks").ToIDispatch() c := oleutil.MustGetProperty(workbooks, "Count").Val for i := 1; i <= int(c); i++ { workbook := oleutil.MustGetProperty(workbooks, "Item", i).ToIDispatch() fullName := oleutil.MustGetProperty(workbook, "FullName").ToString() name := oleutil.MustGetProperty(workbook, "Name").ToString() if strings.HasPrefix(fullName, "https:") && name == filepath.Base(absolutePath) { // If a workbook is opened through a WOPI URL, its absolute file path cannot be retrieved. // If the absolutePath is not writable, it assumes that the workbook has opened by WOPI. if FileIsNotWritable(absolutePath) { return &OleExcel{application: excel, workbook: workbook}, func() { oleutil.MustPutProperty(excel, "EnableEvents", true) oleutil.MustPutProperty(excel, "ScreenUpdating", true) workbook.Release() workbooks.Release() excel.Release() ole.CoUninitialize() runtime.UnlockOSThread() }, nil } else { // This workbook might not be specified with the absolutePath } } else if normalizePath(fullName) == normalizePath(absolutePath) { return &OleExcel{application: excel, workbook: workbook}, func() { oleutil.MustPutProperty(excel, "EnableEvents", true) oleutil.MustPutProperty(excel, "ScreenUpdating", true) workbook.Release() workbooks.Release() excel.Release() ole.CoUninitialize() runtime.UnlockOSThread() }, nil } } return nil, func() {}, fmt.Errorf("workbook not found: %s", absolutePath) } func NewExcelOleWithNewObject(absolutePath string) (*OleExcel, func(), error) { runtime.LockOSThread() ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED) unknown, err := oleutil.CreateObject("Excel.Application") if err != nil { return nil, func() {}, err } excel, err := unknown.QueryInterface(ole.IID_IDispatch) if err != nil { return nil, func() {}, err } workbooks := oleutil.MustGetProperty(excel, "Workbooks").ToIDispatch() workbook, err := oleutil.CallMethod(workbooks, "Open", absolutePath) if err != nil { return nil, func() {}, err } w := workbook.ToIDispatch() return &OleExcel{application: excel, workbook: w}, func() { w.Release() workbooks.Release() excel.Release() oleutil.CallMethod(excel, "Close") ole.CoUninitialize() runtime.UnlockOSThread() }, nil } func (o *OleExcel) GetBackendName() string { return "ole" } func (o *OleExcel) GetSheets() ([]Worksheet, error) { worksheets := oleutil.MustGetProperty(o.workbook, "Worksheets").ToIDispatch() defer worksheets.Release() count := int(oleutil.MustGetProperty(worksheets, "Count").Val) worksheetList := make([]Worksheet, count) for i := 1; i <= count; i++ { worksheet := oleutil.MustGetProperty(worksheets, "Item", i).ToIDispatch() worksheetList[i-1] = &OleWorksheet{ excel: o, worksheet: worksheet, } } return worksheetList, nil } func (o *OleExcel) FindSheet(sheetName string) (Worksheet, error) { worksheets := oleutil.MustGetProperty(o.workbook, "Worksheets").ToIDispatch() defer worksheets.Release() count := int(oleutil.MustGetProperty(worksheets, "Count").Val) for i := 1; i <= count; i++ { worksheet := oleutil.MustGetProperty(worksheets, "Item", i).ToIDispatch() name := oleutil.MustGetProperty(worksheet, "Name").ToString() if name == sheetName { return &OleWorksheet{ excel: o, worksheet: worksheet, }, nil } } return nil, fmt.Errorf("sheet not found: %s", sheetName) } func (o *OleExcel) CreateNewSheet(sheetName string) error { activeWorksheet := oleutil.MustGetProperty(o.workbook, "ActiveSheet").ToIDispatch() defer activeWorksheet.Release() activeWorksheetIndex := oleutil.MustGetProperty(activeWorksheet, "Index").Val worksheets := oleutil.MustGetProperty(o.workbook, "Worksheets").ToIDispatch() defer worksheets.Release() _, err := oleutil.CallMethod(worksheets, "Add", nil, activeWorksheet) if err != nil { return err } worksheet := oleutil.MustGetProperty(worksheets, "Item", activeWorksheetIndex+1).ToIDispatch() defer worksheet.Release() _, err = oleutil.PutProperty(worksheet, "Name", sheetName) if err != nil { return err } return nil } func (o *OleExcel) CopySheet(srcSheetName string, dstSheetName string) error { worksheets := oleutil.MustGetProperty(o.workbook, "Worksheets").ToIDispatch() defer worksheets.Release() srcSheetVariant, err := oleutil.GetProperty(worksheets, "Item", srcSheetName) if err != nil { return fmt.Errorf("faild to get sheet: %w", err) } srcSheet := srcSheetVariant.ToIDispatch() defer srcSheet.Release() srcSheetIndex := oleutil.MustGetProperty(srcSheet, "Index").Val _, err = oleutil.CallMethod(srcSheet, "Copy", nil, srcSheet) if err != nil { return err } dstSheetVariant, err := oleutil.GetProperty(worksheets, "Item", srcSheetIndex+1) if err != nil { return fmt.Errorf("failed to get copied sheet: %w", err) } dstSheet := dstSheetVariant.ToIDispatch() defer dstSheet.Release() _, err = oleutil.PutProperty(dstSheet, "Name", dstSheetName) if err != nil { return err } return nil } func (o *OleExcel) Save() error { _, err := oleutil.CallMethod(o.workbook, "Save") if err != nil { return err } return nil } func (o *OleWorksheet) Release() { o.worksheet.Release() } func (o *OleWorksheet) Name() (string, error) { name := oleutil.MustGetProperty(o.worksheet, "Name").ToString() return name, nil } func (o *OleWorksheet) GetTables() ([]Table, error) { tables := oleutil.MustGetProperty(o.worksheet, "ListObjects").ToIDispatch() defer tables.Release() count := int(oleutil.MustGetProperty(tables, "Count").Val) tableList := make([]Table, count) for i := 1; i <= count; i++ { table := oleutil.MustGetProperty(tables, "Item", i).ToIDispatch() defer table.Release() name := oleutil.MustGetProperty(table, "Name").ToString() defer table.Release() tableRange := oleutil.MustGetProperty(table, "Range").ToIDispatch() defer tableRange.Release() tableList[i-1] = Table{ Name: name, Range: NormalizeRange(oleutil.MustGetProperty(tableRange, "Address").ToString()), } } return tableList, nil } func (o *OleWorksheet) GetPivotTables() ([]PivotTable, error) { pivotTables := oleutil.MustGetProperty(o.worksheet, "PivotTables").ToIDispatch() defer pivotTables.Release() count := int(oleutil.MustGetProperty(pivotTables, "Count").Val) pivotTableList := make([]PivotTable, count) for i := 1; i <= count; i++ { pivotTable := oleutil.MustGetProperty(pivotTables, "Item", i).ToIDispatch() defer pivotTable.Release() name := oleutil.MustGetProperty(pivotTable, "Name").ToString() pivotTableRange := oleutil.MustGetProperty(pivotTable, "TableRange1").ToIDispatch() defer pivotTableRange.Release() pivotTableList[i-1] = PivotTable{ Name: name, Range: NormalizeRange(oleutil.MustGetProperty(pivotTableRange, "Address").ToString()), } } return pivotTableList, nil } func (o *OleWorksheet) SetValue(cell string, value any) error { range_ := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer range_.Release() _, err := oleutil.PutProperty(range_, "Value", value) return err } func (o *OleWorksheet) SetFormula(cell string, formula string) error { range_ := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer range_.Release() _, err := oleutil.PutProperty(range_, "Formula", formula) return err } func (o *OleWorksheet) GetValue(cell string) (string, error) { range_ := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer range_.Release() value := oleutil.MustGetProperty(range_, "Text").Value() switch v := value.(type) { case string: return v, nil case nil: return "", nil default: // Handle other types as needed return "", fmt.Errorf("unsupported type: %T", v) } } func (o *OleWorksheet) GetFormula(cell string) (string, error) { range_ := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer range_.Release() formula := oleutil.MustGetProperty(range_, "Formula").ToString() return formula, nil } func (o *OleWorksheet) GetDimention() (string, error) { range_ := oleutil.MustGetProperty(o.worksheet, "UsedRange").ToIDispatch() defer range_.Release() dimension := oleutil.MustGetProperty(range_, "Address").ToString() return NormalizeRange(dimension), nil } func (o *OleWorksheet) GetPagingStrategy(pageSize int) (PagingStrategy, error) { return NewOlePagingStrategy(1000, o) } func (o *OleWorksheet) PrintArea() (string, error) { v, err := oleutil.GetProperty(o.worksheet, "PageSetup") if err != nil { return "", err } pageSetup := v.ToIDispatch() defer pageSetup.Release() printArea := oleutil.MustGetProperty(pageSetup, "PrintArea").ToString() return printArea, nil } func (o *OleWorksheet) HPageBreaks() ([]int, error) { v, err := oleutil.GetProperty(o.worksheet, "HPageBreaks") if err != nil { return nil, err } hPageBreaks := v.ToIDispatch() defer hPageBreaks.Release() count := int(oleutil.MustGetProperty(hPageBreaks, "Count").Val) pageBreaks := make([]int, count) for i := 1; i <= count; i++ { pageBreak := oleutil.MustGetProperty(hPageBreaks, "Item", i).ToIDispatch() defer pageBreak.Release() location := oleutil.MustGetProperty(pageBreak, "Location").ToIDispatch() defer location.Release() row := oleutil.MustGetProperty(location, "Row").Val pageBreaks[i-1] = int(row) } return pageBreaks, nil } func (o *OleWorksheet) CapturePicture(captureRange string) (string, error) { r := oleutil.MustGetProperty(o.worksheet, "Range", captureRange).ToIDispatch() defer r.Release() _, err := oleutil.CallMethod( r, "CopyPicture", int(1), // xlScreen (https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.office.interop.excel.xlpictureappearance?view=excel-pia) int(2), // xlBitmap (https://learn.microsoft.com/ja-jp/dotnet/api/microsoft.office.interop.excel.xlcopypictureformat?view=excel-pia) ) if err != nil { return "", err } // Read the image from the clipboard buf := new(bytes.Buffer) bufWriter := bufio.NewWriter(buf) clipboardReader, err := clipboard.ReadFromClipboard() if err != nil { return "", fmt.Errorf("failed to read from clipboard: %w", err) } if _, err := io.Copy(bufWriter, clipboardReader); err != nil { return "", fmt.Errorf("failed to copy clipboard data: %w", err) } if err := bufWriter.Flush(); err != nil { return "", fmt.Errorf("failed to flush buffer: %w", err) } return base64.StdEncoding.EncodeToString(buf.Bytes()), nil } func (o *OleWorksheet) AddTable(tableRange string, tableName string) error { tables := oleutil.MustGetProperty(o.worksheet, "ListObjects").ToIDispatch() defer tables.Release() // https://learn.microsoft.com/ja-jp/office/vba/api/excel.listobjects.add tableVar, err := oleutil.CallMethod( tables, "Add", int(1), // xlSrcRange (https://learn.microsoft.com/ja-jp/office/vba/api/excel.xllistobjectsourcetype) tableRange, nil, int(0), // xlYes (https://learn.microsoft.com/ja-jp/office/vba/api/excel.xlyesnoguess) ) if err != nil { return err } table := tableVar.ToIDispatch() defer table.Release() _, err = oleutil.PutProperty(table, "Name", tableName) if err != nil { return err } return err } func (o *OleWorksheet) GetCellStyle(cell string) (*CellStyle, error) { rng := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer rng.Release() style := &CellStyle{} // Get Font information normalStyle := oleutil.MustGetProperty(o.excel.workbook, "Styles", "Normal").ToIDispatch() defer normalStyle.Release() normalFont := oleutil.MustGetProperty(normalStyle, "Font").ToIDispatch() defer normalFont.Release() font := oleutil.MustGetProperty(rng, "Font").ToIDispatch() defer font.Release() normalFontSize := int(oleutil.MustGetProperty(normalFont, "Size").Value().(float64)) normalFontBold := oleutil.MustGetProperty(normalFont, "Bold").Value().(bool) normalFontItalic := oleutil.MustGetProperty(normalFont, "Italic").Value().(bool) normalFontColor := oleutil.MustGetProperty(normalFont, "Color").Value().(float64) fontSize := int(oleutil.MustGetProperty(font, "Size").Value().(float64)) fontBold := oleutil.MustGetProperty(font, "Bold").Value().(bool) fontItalic := oleutil.MustGetProperty(font, "Italic").Value().(bool) fontColor := oleutil.MustGetProperty(font, "Color").Value().(float64) if fontSize != normalFontSize || fontBold != normalFontBold || fontItalic != normalFontItalic || fontColor != normalFontColor { colorStr := bgrToRgb(fontColor) style.Font = &FontStyle{ Bold: &fontBold, Italic: &fontItalic, Size: &fontSize, Color: &colorStr, } } // Get Interior (fill) information interior := oleutil.MustGetProperty(rng, "Interior").ToIDispatch() defer interior.Release() interiorPattern := excelPatternToFillPattern(oleutil.MustGetProperty(interior, "Pattern").Value().(int32)) if interiorPattern != FillPatternNone { interiorColor := oleutil.MustGetProperty(interior, "Color").Value().(float64) style.Fill = &FillStyle{ Type: "pattern", Pattern: interiorPattern, Color: []string{bgrToRgb(interiorColor)}, } } // Get Border information var borderStyles []Border // Get borders for each direction: Left(7), Top(8), Bottom(9), Right(10) borderPositions := []struct { index int position BorderType }{ {7, BorderTypeLeft}, {8, BorderTypeTop}, {9, BorderTypeBottom}, {10, BorderTypeRight}, } borders := oleutil.MustGetProperty(rng, "Borders").ToIDispatch() defer borders.Release() bordersLineStyle := oleutil.MustGetProperty(borders, "LineStyle") if bordersLineStyle.VT == ole.VT_NULL { // If Borders.LineStyle is null, the borders have different styles for _, pos := range borderPositions { border := oleutil.MustGetProperty(borders, "Item", pos.index).ToIDispatch() defer border.Release() borderLineStyle := excelBorderStyleToName(oleutil.MustGetProperty(border, "LineStyle").Value().(int32)) if borderLineStyle != BorderStyleNone { borderColor := oleutil.MustGetProperty(border, "Color").Value().(float64) borderStyle := Border{ Type: pos.position, Style: borderLineStyle, Color: bgrToRgb(borderColor), } borderStyles = append(borderStyles, borderStyle) } } } else { // If Borders.LineStyle is not null, all borders have the same style lineStyle := excelBorderStyleToName(bordersLineStyle.Value().(int32)) if lineStyle != BorderStyleNone { for _, pos := range borderPositions { border := oleutil.MustGetProperty(borders, "Item", pos.index).ToIDispatch() borderColor := oleutil.MustGetProperty(border, "Color").Value().(float64) borderStyle := Border{ Type: pos.position, Style: lineStyle, Color: bgrToRgb(borderColor), } borderStyles = append(borderStyles, borderStyle) } } } style.Border = borderStyles // Get NumberFormat information generalNumberFormat := oleutil.MustGetProperty(o.excel.application, "International", 26).Value().(string) // xlGeneralFormatName numberFormat := oleutil.MustGetProperty(rng, "NumberFormat").ToString() if numberFormat != generalNumberFormat && numberFormat != "@" { style.NumFmt = &numberFormat } // Extract decimal places from number format if it's a numeric format decimalPlaces := extractDecimalPlacesFromFormat(numberFormat) style.DecimalPlaces = &decimalPlaces return style, nil } // bgrToRgb converts BGR color format to RGB hex string func bgrToRgb(bgrColor float64) string { bgrColorInt := int32(bgrColor) // Extract RGB components from BGR format r := (bgrColorInt >> 0) & 0xFF g := (bgrColorInt >> 8) & 0xFF b := (bgrColorInt >> 16) & 0xFF return fmt.Sprintf("#%02X%02X%02X", r, g, b) } // excelBorderStyleToName converts Excel border style constant to BorderStyleName func excelBorderStyleToName(excelStyle int32) BorderStyle { switch excelStyle { case 1: // xlContinuous return BorderStyleContinuous case -4115: // xlDash return BorderStyleDash case -4118: // xlDot return BorderStyleDot case -4119: // xlDouble return BorderStyleDouble case 4: // xlDashDot return BorderStyleDashDot case 5: // xlDashDotDot return BorderStyleDashDotDot case 13: // xlSlantDashDot return BorderStyleSlantDashDot case -4142: // xlLineStyleNone return BorderStyleNone default: return BorderStyleNone } } // excelPatternToFillPattern converts Excel XlPattern constant to FillPatternName func excelPatternToFillPattern(excelPattern int32) FillPattern { switch excelPattern { case -4142: // xlPatternNone return FillPatternNone case 1: // xlPatternSolid return FillPatternSolid case -4125: // xlPatternGray75 return FillPatternDarkGray case -4124: // xlPatternGray50 return FillPatternMediumGray case -4126: // xlPatternGray25 return FillPatternLightGray case -4121: // xlPatternGray16 return FillPatternGray125 case -4127: // xlPatternGray8 return FillPatternGray0625 case 9: // xlPatternHorizontal return FillPatternLightHorizontal case 12: // xlPatternVertical return FillPatternLightVertical case 10: // xlPatternDown return FillPatternLightDown case 11: // xlPatternUp return FillPatternLightUp case 16: // xlPatternGrid return FillPatternLightGrid case 17: // xlPatternCrissCross return FillPatternLightTrellis case 5: // xlPatternLightHorizontal return FillPatternLightHorizontal case 6: // xlPatternLightVertical return FillPatternLightVertical case 7: // xlPatternLightDown return FillPatternLightDown case 8: // xlPatternLightUp return FillPatternLightUp case 15: // xlPatternLightGrid return FillPatternLightGrid case 18: // xlPatternLightTrellis return FillPatternLightTrellis case 13: // xlPatternSemiGray75 return FillPatternDarkHorizontal case 2: // xlPatternDarkHorizontal return FillPatternDarkHorizontal case 3: // xlPatternDarkVertical return FillPatternDarkVertical case 4: // xlPatternDarkDown return FillPatternDarkDown case 14: // xlPatternDarkUp return FillPatternDarkUp case -4162: // xlPatternDarkGrid return FillPatternDarkGrid case -4166: // xlPatternDarkTrellis return FillPatternDarkTrellis default: return FillPatternNone } } var extractDecimalPlacesRegexp = regexp.MustCompile(`\.([0#]+)`) // extractDecimalPlacesFromFormat extracts decimal places count from Excel number format string func extractDecimalPlacesFromFormat(format string) int { // Handle common numeric formats // Examples: "0.00" -> 2, "#,##0.000" -> 3, "0" -> 0 matches := extractDecimalPlacesRegexp.FindStringSubmatch(format) if len(matches) > 1 { return len(matches[1]) } return 0 } func (o *OleWorksheet) SetCellStyle(cell string, style *CellStyle) error { rng := oleutil.MustGetProperty(o.worksheet, "Range", cell).ToIDispatch() defer rng.Release() // Apply Font styles if style.Font != nil { font := oleutil.MustGetProperty(rng, "Font").ToIDispatch() defer font.Release() if style.Font.Bold != nil { oleutil.PutProperty(font, "Bold", *style.Font.Bold) } if style.Font.Italic != nil { oleutil.PutProperty(font, "Italic", *style.Font.Italic) } if style.Font.Size != nil && *style.Font.Size > 0 { oleutil.PutProperty(font, "Size", *style.Font.Size) } if style.Font.Color != nil && *style.Font.Color != "" { colorValue := rgbToBgr(*style.Font.Color) oleutil.PutProperty(font, "Color", colorValue) } if style.Font.Strike != nil && *style.Font.Strike { oleutil.PutProperty(font, "Strikethrough", true) } } // Apply Fill styles if style.Fill != nil { interior := oleutil.MustGetProperty(rng, "Interior").ToIDispatch() defer interior.Release() if style.Fill.Pattern != FillPatternNone { oleutil.PutProperty(interior, "Pattern", fillPatternToExcelPattern(style.Fill.Pattern)) } if len(style.Fill.Color) > 0 && style.Fill.Color[0] != "" { colorValue := rgbToBgr(style.Fill.Color[0]) oleutil.PutProperty(interior, "Color", colorValue) } } // Apply Border styles if len(style.Border) > 0 { borders := oleutil.MustGetProperty(rng, "Borders").ToIDispatch() defer borders.Release() for _, borderStyle := range style.Border { borderIndex := borderTypeToIndex(borderStyle.Type) if borderIndex > 0 { border := oleutil.MustGetProperty(borders, "Item", borderIndex).ToIDispatch() defer border.Release() oleutil.PutProperty(border, "LineStyle", borderStyleNameToExcel(borderStyle.Style)) if borderStyle.Color != "" { colorValue := rgbToBgr(borderStyle.Color) oleutil.PutProperty(border, "Color", colorValue) } } } } // Apply Number Format if style.NumFmt != nil && *style.NumFmt != "" { oleutil.PutProperty(rng, "NumberFormat", *style.NumFmt) } return nil } // rgbToBgr converts RGB hex string to BGR color format func rgbToBgr(rgbColor string) int32 { if len(rgbColor) != 7 || rgbColor[0] != '#' { return 0 } r := hexToByte(rgbColor[1:3]) g := hexToByte(rgbColor[3:5]) b := hexToByte(rgbColor[5:7]) return int32(r) | (int32(g) << 8) | (int32(b) << 16) } // hexToByte converts hex string to byte func hexToByte(hex string) byte { var result byte for _, char := range hex { result *= 16 if char >= '0' && char <= '9' { result += byte(char - '0') } else if char >= 'A' && char <= 'F' { result += byte(char - 'A' + 10) } else if char >= 'a' && char <= 'f' { result += byte(char - 'a' + 10) } } return result } // borderTypeToIndex converts border type string to Excel border index func borderTypeToIndex(borderType BorderType) int { switch borderType { case BorderTypeLeft: return 7 case BorderTypeTop: return 8 case BorderTypeBottom: return 9 case BorderTypeRight: return 10 case BorderTypeDiagonalDown: return 5 case BorderTypeDiagonalUp: return 6 default: return 0 } } // borderStyleNameToExcel converts BorderStyleName to Excel constant func borderStyleNameToExcel(style BorderStyle) int32 { switch style { case BorderStyleContinuous: return 1 // xlContinuous case BorderStyleDash: return -4115 // xlDash case BorderStyleDot: return -4118 // xlDot case BorderStyleDouble: return -4119 // xlDouble case BorderStyleDashDot: return 4 // xlDashDot case BorderStyleDashDotDot: return 5 // xlDashDotDot case BorderStyleSlantDashDot: return 13 // xlSlantDashDot case BorderStyleNone: return -4142 // xlLineStyleNone default: return -4142 // xlLineStyleNone } } // fillPatternToExcelPattern converts FillPatternName to Excel pattern constant func fillPatternToExcelPattern(pattern FillPattern) int32 { switch pattern { case FillPatternSolid: return 1 // xlPatternSolid case FillPatternMediumGray: return -4124 // xlPatternGray50 case FillPatternDarkGray: return -4125 // xlPatternGray75 case FillPatternLightGray: return -4126 // xlPatternGray25 case FillPatternGray125: return -4121 // xlPatternGray16 case FillPatternGray0625: return -4127 // xlPatternGray8 case FillPatternLightHorizontal: return 5 // xlPatternLightHorizontal case FillPatternLightVertical: return 6 // xlPatternLightVertical case FillPatternLightDown: return 7 // xlPatternLightDown case FillPatternLightUp: return 8 // xlPatternLightUp case FillPatternLightGrid: return 15 // xlPatternLightGrid case FillPatternLightTrellis: return 18 // xlPatternLightTrellis case FillPatternDarkHorizontal: return 2 // xlPatternDarkHorizontal case FillPatternDarkVertical: return 3 // xlPatternDarkVertical case FillPatternDarkDown: return 4 // xlPatternDarkDown case FillPatternDarkUp: return 14 // xlPatternDarkUp case FillPatternDarkGrid: return -4162 // xlPatternDarkGrid case FillPatternDarkTrellis: return -4166 // xlPatternDarkTrellis case FillPatternNone: return -4142 // xlPatternNone default: return -4142 // xlPatternNone } } func normalizePath(path string) string { // Normalize the volume name to uppercase vol := filepath.VolumeName(path) if vol == "" { return path } rest := path[len(vol):] return filepath.Clean(strings.ToUpper(vol) + rest) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/negokaz/excel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server