1package mcp23import (4 "context"5 "errors"6 "fmt"7 "os"89 sdk "github.com/modelcontextprotocol/go-sdk/mcp"1011 "src.rest/cat/012/internal/confine"12 "src.rest/cat/012/internal/headless"13 "src.rest/cat/012/internal/sheet"14)1516// create_workbook makes a new .012 file in a folder open to the server:17// empty, from a file 012 imports (which the other tools only read), a18// copy of another workbook, or holding a table of typed values. It19// never replaces a file.2021type createIn struct {22 Path string `json:"path" jsonschema:"the new workbook's file, ending in .012; relative to the folder open to the server, or absolute inside it"`23 From string `json:"from,omitempty" jsonschema:"a file to make it from: one 012 imports (CSV, XLSX, JSON, SQLite...) or another workbook; empty for an empty workbook"`24 Data []headless.Row `json:"data,omitempty" jsonschema:"instead of from: a table for its first sheet, rows written at A1 with their types and column formats, as write_table writes them"`25 Columns []string `json:"columns,omitempty" jsonschema:"with data: the columns' names in order, as write_table takes them"`26}2728func (s *Server) addCreateTool() {29 tool(s, &sdk.Tool{Name: "create_workbook", Annotations: writes,30 Description: "Make a new .012 workbook, empty, from a file 012 imports (CSV, XLSX, JSON, SQLite...), or with data, a table of typed values as write_table writes it; it can then be changed, and it describes the workbook made. It won't replace a file that exists."},31 s.createWorkbook)32}3334func (s *Server) createWorkbook(ctx context.Context, req *sdk.CallToolRequest, in createIn) (*sdk.CallToolResult, describeOut, error) {35 roots := s.roots(ctx, req.Session)36 path, err := resolve(roots, in.Path)37 if err != nil {38 return nil, describeOut{}, err39 }40 if own, _ := isWorkbook(path); !own {41 return nil, describeOut{}, fmt.Errorf("%s: a workbook's file name ends in %s", in.Path, sheet.FileExt)42 }43 if _, err := os.Lstat(path); err == nil {44 return nil, describeOut{}, fmt.Errorf("%s exists: describe it, or name a new file", in.Path)45 }46 f, err := headless.Open(path, true)47 if err != nil {48 return nil, describeOut{}, err49 }50 switch {51 case in.From != "" && in.Data != nil:52 return nil, describeOut{}, errors.New("give from or data, not both: write_table adds data to a workbook made from a file")53 case in.From != "":54 if f.Book, err = s.source(ctx, roots, in.From); err != nil {55 return nil, describeOut{}, err56 }57 case in.Data != nil:58 spec := headless.TableSpec{At: sheet.Qualified(f.Book.Sheet(0).Name(), sheet.Rect{}), Columns: in.Columns, Rows: in.Data}59 if _, err := headless.WriteTable(f.Book, spec, headless.SetOptions{}); err != nil {60 return nil, describeOut{}, err61 }62 }63 save := f.Save64 if s.opts.Save != nil {65 save = func() error { return s.opts.Save(f) }66 }67 if err := save(); err != nil {68 return nil, describeOut{}, err69 }70 b := &book{Backend: s.backend(path), path: path, name: displayName(roots, path)}71 s.used(ctx, b)72 d := headless.Describe(f.Book)73 return nil, describeOut{Path: b.name, Description: &d}, nil74}7576// source is the workbook a new one is made from: a file imported, or77// another workbook's contents.78func (s *Server) source(ctx context.Context, roots []confine.Root, name string) (*sheet.Workbook, error) {79 path, err := resolve(roots, name)80 if err != nil {81 return nil, err82 }83 own, readable := isWorkbook(path)84 switch {85 case own:86 f, err := headless.Open(path, false)87 if err != nil {88 return nil, err89 }90 return f.Book, nil91 case readable:92 return importBook(ctx, path)93 }94 return nil, fmt.Errorf("%s isn't a file 012 imports", name)95}