Files
task-master/internal/mcp/token.go
T
skrueckenandCursor 846bb31dc9
CI / test-and-build (push) Failing after 1m25s
CI / demo (push) Skipped
Add stdio MCP server for token-scoped ticket access.
Expose tm mcp tools against the local daemon, document agent setup, and
ignore workspace .tm_token files.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-01 16:56:11 +01:00

80 lines
1.7 KiB
Go

package mcp
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
const tokenFileName = ".tm_token"
// TokenFile holds credentials from a workspace .tm_token file.
type TokenFile struct {
Project string
Token string
Path string
}
// FindTokenFile walks from start (usually cwd) upward looking for .tm_token.
func FindTokenFile(start string) (TokenFile, error) {
dir, err := filepath.Abs(start)
if err != nil {
return TokenFile{}, err
}
for {
path := filepath.Join(dir, tokenFileName)
if st, err := os.Stat(path); err == nil && !st.IsDir() {
tf, err := ParseTokenFile(path)
if err != nil {
return TokenFile{}, err
}
return tf, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
return TokenFile{}, fmt.Errorf("no %s found (create an MCP token in the TUI with g, then place project=… and token=… in %s)", tokenFileName, tokenFileName)
}
// ParseTokenFile reads project= and token= lines from path.
func ParseTokenFile(path string) (TokenFile, error) {
f, err := os.Open(path)
if err != nil {
return TokenFile{}, err
}
defer f.Close()
tf := TokenFile{Path: path}
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, val, ok := strings.Cut(line, "=")
if !ok {
continue
}
key = strings.TrimSpace(strings.ToLower(key))
val = strings.TrimSpace(val)
switch key {
case "project":
tf.Project = val
case "token":
tf.Token = val
}
}
if err := sc.Err(); err != nil {
return TokenFile{}, err
}
if tf.Project == "" || tf.Token == "" {
return TokenFile{}, fmt.Errorf("%s must contain project= and token= lines", path)
}
return tf, nil
}