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>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user