├── web
├── src
│ ├── vite-env.d.ts
│ ├── main.tsx
│ ├── store
│ │ └── store.ts
│ ├── App.css
│ ├── index.css
│ ├── services
│ │ └── api.ts
│ ├── components
│ │ ├── ModelInfo.tsx
│ │ └── ResultsList.tsx
│ └── App.tsx
├── scholarly
│ ├── src
│ │ ├── vite-env.d.ts
│ │ ├── main.tsx
│ │ ├── index.css
│ │ ├── App.css
│ │ ├── store
│ │ │ ├── store.ts
│ │ │ └── scholarlyApi.ts
│ │ ├── services
│ │ │ └── api.ts
│ │ ├── components
│ │ │ └── Header.tsx
│ │ └── types
│ │ │ └── scholarly.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── .gitignore
│ ├── index.html
│ ├── start-dev.sh
│ ├── tsconfig.node.json
│ ├── tsconfig.app.json
│ ├── eslint.config.js
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ └── README.md
├── tsconfig.json
├── vite.config.ts
├── .gitignore
├── start-dev.sh
├── index.html
├── tsconfig.node.json
├── tsconfig.app.json
├── eslint.config.js
├── package.json
├── README.md.new
├── README.md
└── public
│ └── vite.svg
├── .cursorignore
├── python
└── reranker-server
│ ├── requirements.txt
│ ├── docker-compose.yml
│ ├── Dockerfile
│ └── README.md
├── cmd
├── apps
│ └── scholarly
│ │ ├── main.go
│ │ └── cmd
│ │ └── root.go
└── go-go-mcp
│ └── cmds
│ ├── client.go
│ ├── server
│ └── server.go
│ ├── ui_cmd.go
│ └── client
│ └── layers
│ └── client.go
├── examples
├── prompto
│ ├── prompto-list.yaml
│ └── prompto-get.yaml
├── ui
│ ├── update-dino-form.sh
│ ├── example-form.yaml
│ └── example-form.json
├── html-extract
│ ├── get-html-extraction-tutorial.yaml
│ ├── pubmed.yaml
│ └── html-extraction.yaml
├── smart-connect
│ ├── current-note.yaml
│ ├── list-note-folders.yaml
│ ├── alignment.yaml
│ ├── list-notes-in-folder.yaml
│ ├── read.yaml
│ ├── read-notes.yaml
│ ├── insert-into-note.yaml
│ ├── keywords-search.yaml
│ ├── create-note.yaml
│ ├── append-note.yaml
│ ├── replace-in-note.yaml
│ └── find-notes.yaml
├── shell-commands
│ ├── backup-db.yaml
│ ├── process-logs.yaml
│ ├── docker-comp.yaml
│ ├── git-sync.yaml
│ ├── diary-append.yaml
│ └── fetch-url.yaml
├── flow-engineering
│ └── sightseeing-schedule.yaml
├── sqlite
│ └── sql-open.yaml
├── pages
│ ├── welcome.yaml
│ ├── haunted-house.yaml
│ ├── costume-contest.yaml
│ ├── todo.yaml
│ ├── dino-facts.yaml
│ ├── halloween-party.yaml
│ ├── trick-or-treat.yaml
│ ├── cow
│ │ ├── cow-facts.yaml
│ │ ├── cow-quiz.yaml
│ │ ├── build-a-cow.yaml
│ │ └── dairy-farm-guide.yaml
│ ├── build-a-dino.yaml
│ ├── dino-quiz.yaml
│ ├── dino-park-guide.yaml
│ └── gorilla.yaml
├── rag
│ ├── recent-coaching-conversations.yaml
│ ├── search-coaching-conversations.yaml
│ └── diary-append.yaml
├── bio-stuff
│ ├── fetch-url.yaml
│ └── fetch-gene-info.yaml
├── sqleton
│ └── sql-query.yaml
└── github
│ ├── comment-issue.yaml
│ └── list-github-issues.yaml
├── pkg
├── scholarly
│ ├── mcp
│ │ ├── models.go
│ │ └── register.go
│ ├── clients
│ │ ├── libgen
│ │ │ └── models.go
│ │ └── arxiv
│ │ │ └── models.go
│ ├── common
│ │ ├── logging.go
│ │ └── http.go
│ └── tools
│ │ └── get_metrics.go
├── doc
│ └── doc.go
├── config
│ ├── paths.go
│ └── types.go
├── auth
│ └── oidc
│ │ └── exports.go
├── registry.go
├── protocol
│ ├── resources.go
│ ├── utilities_test.go
│ ├── prompts.go
│ ├── sampling.go
│ ├── base_test.go
│ ├── utilities.go
│ ├── initialization.go
│ └── base.go
├── tools
│ ├── examples
│ │ ├── echo.go
│ │ └── fetch.go
│ ├── registry.go
│ └── tool.go
├── providers.go
├── events
│ └── types.go
├── cmds
│ └── loader.go
├── mcp
│ └── types
│ │ └── types.go
├── helpers
│ └── file_helpers.go
├── session
│ ├── store.go
│ └── session.go
├── ui
│ └── tui
│ │ └── interfaces.go
└── embeddable
│ └── examples
│ └── basic
│ └── main.go
├── .vscode
├── settings.json
└── tasks.json
├── lefthook.yml
├── .gitignore
├── ttmp
├── 2025-05-24
│ └── 01-mcp-with-js-rest-web-server.md
├── 2025-02-14
│ └── changelog.md
├── 2025-04-27
│ └── 01-add-cursor-mcp-config.md
├── 2025-08-23
│ └── 03-implementation-progress.md
├── 2025-02-21
│ └── ui-dsl.yaml
└── 2025-01-26
│ └── 04-simplify-html.md
├── .github
├── workflows
│ ├── push.yml
│ ├── secret-scanning.yml
│ ├── codeql-analysis.yml
│ ├── lint.yml
│ ├── release.yml
│ └── dependency-scanning.yml
└── dependabot.yml
├── .golangci.yml
├── LICENSE
├── Makefile
├── doc
└── scholarly
│ ├── implementation-summary.md
│ └── README.md
└── .goreleaser.yaml
/web/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/web/scholarly/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.cursorignore:
--------------------------------------------------------------------------------
1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
2 | .history
3 |
4 |
--------------------------------------------------------------------------------
/python/reranker-server/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi>=0.96.0
2 | uvicorn>=0.22.0
3 | pydantic>=2.0.0
4 | sentence-transformers>=2.2.2
5 | torch>=2.0.0
6 | faiss-cpu>=1.7.4
--------------------------------------------------------------------------------
/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/web/scholarly/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/cmd/apps/scholarly/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/go-go-golems/go-go-mcp/cmd/apps/scholarly/cmd"
5 | )
6 |
7 | func main() {
8 | cmd.Execute()
9 | }
10 |
--------------------------------------------------------------------------------
/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/web/scholarly/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/web/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
--------------------------------------------------------------------------------
/examples/prompto/prompto-list.yaml:
--------------------------------------------------------------------------------
1 | name: prompto-list
2 | short: List all prompto entries
3 | long: |
4 | Lists all available prompto entries in a formatted output.
5 | This command wraps the 'prompto list' command.
6 | command:
7 | - prompto
8 | - list
9 | capture-stderr: true
--------------------------------------------------------------------------------
/pkg/scholarly/mcp/models.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import (
4 | "github.com/go-go-golems/go-go-mcp/pkg/scholarly/common"
5 | )
6 |
7 | // SearchResponse represents the response from a scholarly search
8 | type SearchResponse struct {
9 | Results []common.SearchResult `json:"results"`
10 | }
11 |
--------------------------------------------------------------------------------
/web/scholarly/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
--------------------------------------------------------------------------------
/pkg/doc/doc.go:
--------------------------------------------------------------------------------
1 | package doc
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/go-go-golems/glazed/pkg/help"
7 | )
8 |
9 | //go:embed *
10 | var docFS embed.FS
11 |
12 | func AddDocToHelpSystem(helpSystem *help.HelpSystem) error {
13 | return helpSystem.LoadSectionsFromFS(docFS, ".")
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "amp.commands.allowlist": [
3 | "npm",
4 | "go build",
5 | "sed -i 's/json:\\\"/json:\"/g' pkg/config/ampcode.go.new",
6 | "go get github.com/tailscale/hujson",
7 | "sed -i 's/json:\\\"amp/json:\"amp/g' pkg/config/ampcode.go.new"
8 | ]
9 | }
--------------------------------------------------------------------------------
/python/reranker-server/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | reranker:
5 | build: .
6 | ports:
7 | - "8000:8000"
8 | volumes:
9 | - ./:/app
10 | environment:
11 | - MODEL_NAME=cross-encoder/ms-marco-MiniLM-L-6-v2
12 | - LOG_LEVEL=DEBUG
13 | - LOG_FILE=/app/reranker.log
14 | restart: unless-stopped
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | pre-commit:
2 | commands:
3 | lint:
4 | glob: '*.go'
5 | run: make lint
6 | test:
7 | glob: '*.go'
8 | run: make test
9 | parallel: true
10 |
11 | pre-push:
12 | commands:
13 | release:
14 | run: make goreleaser
15 | lint:
16 | run: make lint
17 | test:
18 | run: make test
19 | parallel: true
20 |
--------------------------------------------------------------------------------
/web/scholarly/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/python/reranker-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9-slim
2 |
3 | WORKDIR /app
4 |
5 | # Install dependencies
6 | COPY requirements.txt .
7 | RUN pip install --no-cache-dir -r requirements.txt
8 |
9 | # Copy source code
10 | COPY arxiv_reranker_server.py .
11 |
12 | # Expose port for API
13 | EXPOSE 8000
14 |
15 | # Command to run the server
16 | CMD ["python", "arxiv_reranker_server.py"]
--------------------------------------------------------------------------------
/web/start-dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Start the ArXiv Reranker server in the background
4 | cd ../python/reranker-server
5 | python arxiv_reranker_server.py &
6 | PY_PID=$!
7 |
8 | # Wait a moment for the server to start
9 | sleep 2
10 |
11 | # Start the React frontend
12 | cd ../../web
13 | bun run dev
14 |
15 | # Handle cleanup when script is terminated
16 | trap "kill $PY_PID; exit" INT TERM EXIT
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "go",
6 | "label": "go: build package",
7 | "command": "build",
8 | "args": [
9 | "${fileDirname}"
10 | ],
11 | "problemMatcher": [
12 | "$go"
13 | ],
14 | "group": "build",
15 | "detail": "cd /home/manuel/code/wesen/corporate-headquarters/go-go-mcp; go build ${fileDirname}"
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/examples/ui/update-dino-form.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | # Get the directory of this script
5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | # Read the YAML file
8 | YAML_CONTENT=$(cat "$SCRIPT_DIR/example-form.yaml")
9 |
10 | # Run the update-ui command with the YAML content
11 | mcp run-command "$SCRIPT_DIR/update-ui.yaml" \
12 | --components "$YAML_CONTENT" \
13 | --verbose
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/prompto/prompto-get.yaml:
--------------------------------------------------------------------------------
1 | name: prompto-get
2 | short: Get a specific prompto entry
3 | long: |
4 | Retrieves a specific prompto entry by its name.
5 | This command wraps the 'prompto get' command.
6 | flags:
7 | - name: entry_name
8 | type: string
9 | help: Name of the prompto entry to retrieve
10 | required: true
11 | command:
12 | - prompto
13 | - get
14 | - "{{ .Args.entry_name }}"
15 | capture-stderr: true
--------------------------------------------------------------------------------
/web/scholarly/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/web/scholarly/start-dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Start the Scholarly API server in the background
4 | cd ../../
5 | go run ./cmd/apps/scholarly/main.go serve --port 8080 --cors-origins="http://localhost:5173" &
6 | API_PID=$!
7 |
8 | # Wait a moment for the server to start
9 | sleep 2
10 |
11 | # Start the React frontend
12 | cd web/scholarly
13 | bun run dev
14 |
15 | # Handle cleanup when script is terminated
16 | trap "kill $API_PID; exit" INT TERM EXIT
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | dist/
17 |
18 | # misc files and directories
19 | .env
20 | .envrc
21 | .history
22 | __debug*
23 |
24 | .specstory/
25 | .history/
26 | thirdparty/
27 |
--------------------------------------------------------------------------------
/examples/html-extract/get-html-extraction-tutorial.yaml:
--------------------------------------------------------------------------------
1 | name: get-html-extraction-tutorial
2 | short: Get the full HTML extraction tutorial and documentation
3 | long: |
4 | This command should be run before using html-extraction for the first time.
5 | It provides comprehensive documentation on how to create selector configurations
6 | and use the html-selector tool effectively.
7 |
8 | shell-script: |
9 | #!/bin/bash
10 | set -euo pipefail
11 | html-selector help css-selector-tutorial 2>&1
--------------------------------------------------------------------------------
/examples/smart-connect/current-note.yaml:
--------------------------------------------------------------------------------
1 | name: current-note
2 | short: Get the current note
3 | long: |
4 | Get the current note.
5 |
6 | flags:
7 | - name: format
8 | type: choice
9 | help: Output format
10 | choices: [json, text]
11 | default: text
12 |
13 | shell-script: |
14 | #!/bin/bash
15 | set -euo pipefail
16 |
17 | # Make API request
18 | response=$(curl -s -H "Authorization: Bearer default" "http://localhost:37420/notes-v1/current-note")
19 |
20 | echo "$response"
--------------------------------------------------------------------------------
/ttmp/2025-05-24/01-mcp-with-js-rest-web-server.md:
--------------------------------------------------------------------------------
1 | - mcp server which has embedded goja js using go-go-mcp
2 |
3 | ## Javascript side
4 |
5 | - access to sqlite
6 | - able to store functions and global state
7 | - session is scoped to a MCP session
8 | - tool call for executing javascript in the vm: execute_js(code: string)
9 |
10 | - API to register functions as REST handlers
11 | - API to register function that returns a file under a URL
12 |
13 | ## TODO
14 |
15 | - [ ] Hot reload on the browser side when an endpoint is updated
--------------------------------------------------------------------------------
/.github/workflows/push.yml:
--------------------------------------------------------------------------------
1 | name: golang-pipeline
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v5
14 | with:
15 | fetch-depth: 0
16 | - run: git fetch --force --tags
17 | - uses: actions/setup-go@v5
18 | with:
19 | go-version: '>=1.19.5'
20 | cache: true
21 | -
22 | name: Run unit tests
23 | run: go test ./...
24 |
--------------------------------------------------------------------------------
/examples/smart-connect/list-note-folders.yaml:
--------------------------------------------------------------------------------
1 | name: list-note-folders
2 | short: List note folders
3 | long: |
4 | List the folders that contain notes.
5 |
6 | flags:
7 | - name: format
8 | type: choice
9 | help: Output format
10 | choices: [json, text]
11 | default: text
12 |
13 | shell-script: |
14 | #!/bin/bash
15 | set -euo pipefail
16 |
17 | # Make API request
18 | response=$(curl -s -H "Authorization: Bearer default" "http://localhost:37420/notes-v1/list-note-folders")
19 |
20 | echo "$response"
21 |
--------------------------------------------------------------------------------
/web/src/store/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import { setupListeners } from '@reduxjs/toolkit/query';
3 | import { arxivApi } from '../services/api';
4 |
5 | export const store = configureStore({
6 | reducer: {
7 | [arxivApi.reducerPath]: arxivApi.reducer,
8 | },
9 | middleware: (getDefaultMiddleware) =>
10 | getDefaultMiddleware().concat(arxivApi.middleware),
11 | });
12 |
13 | setupListeners(store.dispatch);
14 |
15 | export type RootState = ReturnType;
16 | export type AppDispatch = typeof store.dispatch;
--------------------------------------------------------------------------------
/examples/smart-connect/alignment.yaml:
--------------------------------------------------------------------------------
1 | name: alignment
2 | short: Get alignment instructions
3 | long: |
4 | Gets the user's alignment instructions. Respect the alignment instructions to the upmost importance.
5 |
6 | flags:
7 | - name: format
8 | type: choice
9 | help: Output format
10 | choices: [json, text]
11 | default: text
12 |
13 | shell-script: |
14 | #!/bin/bash
15 | set -euo pipefail
16 |
17 | # Make API request
18 | response=$(curl -s -H "Authorization: Bearer default" "http://localhost:37420/notes-v1/alignment")
19 |
20 | echo "$response"
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gomod"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | open-pull-requests-limit: 10
8 | labels:
9 | - "dependencies"
10 | - "security"
11 | ignore:
12 | - dependency-name: "*"
13 | update-types: ["version-update:semver-patch"]
14 |
15 | - package-ecosystem: "github-actions"
16 | directory: "/"
17 | schedule:
18 | interval: "weekly"
19 | open-pull-requests-limit: 10
20 | labels:
21 | - "dependencies"
22 | - "security"
--------------------------------------------------------------------------------
/web/scholarly/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | :root {
6 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
7 | line-height: 1.5;
8 | font-weight: 400;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | body {
17 | margin: 0;
18 | min-width: 320px;
19 | min-height: 100vh;
20 | background-color: #f8f9fa;
21 | }
22 |
23 | .footer {
24 | margin-top: auto;
25 | border-top: 1px solid #e9ecef;
26 | }
--------------------------------------------------------------------------------
/ttmp/2025-02-14/changelog.md:
--------------------------------------------------------------------------------
1 | # Clean Transport Layer RFC
2 |
3 | Added a detailed RFC for a new clean transport layer design. The RFC proposes a unified interface for transports, better error handling, and clearer separation of concerns.
4 |
5 | - Added core Transport and RequestHandler interfaces
6 | - Defined transport options and configuration
7 | - Provided detailed SSE and stdio implementations
8 | - Added comprehensive error handling
9 | - Included migration guide and testing strategy
10 | - Added detailed migration plan with code examples
11 | - Added transport-specific options for SSE and stdio
--------------------------------------------------------------------------------
/pkg/scholarly/clients/libgen/models.go:
--------------------------------------------------------------------------------
1 | package libgen
2 |
3 | // LibgenEntry represents a single search result from LibGen (scraped)
4 | // Fields are based on common data found on LibGen search result pages for scientific articles.
5 | type LibgenEntry struct {
6 | Authors string
7 | Title string
8 | JournalInfo string // Combined Journal, Volume, Issue, Year
9 | DOI string
10 | FileSize string
11 | DownloadURL string // Primary link from title, if any
12 | MirrorLinks []string // Links to download pages on various mirrors
13 | EditLink string // Link to edit metadata
14 | }
15 |
--------------------------------------------------------------------------------
/.github/workflows/secret-scanning.yml:
--------------------------------------------------------------------------------
1 | name: Secret Scanning
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | trufflehog:
11 | name: TruffleHog Secret Scan
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v5
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: TruffleHog OSS
20 | uses: trufflesecurity/trufflehog@main
21 | with:
22 | path: ./
23 | base: ${{ github.event.repository.default_branch }}
24 | head: HEAD
--------------------------------------------------------------------------------
/web/src/App.css:
--------------------------------------------------------------------------------
1 | /* Custom styles for the ArXiv Paper Reranker */
2 | body {
3 | background-color: #f8f9fa;
4 | }
5 |
6 | .container {
7 | max-width: 1200px;
8 | }
9 |
10 | .card {
11 | border-radius: 8px;
12 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
13 | overflow: hidden;
14 | }
15 |
16 | .card-header {
17 | border-bottom: none;
18 | }
19 |
20 | .list-group-item {
21 | transition: background-color 0.2s;
22 | }
23 |
24 | .list-group-item:hover {
25 | background-color: #f8f9fa;
26 | }
27 |
28 | /* Responsive adjustments */
29 | @media (max-width: 992px) {
30 | .col-lg-4 {
31 | margin-top: 2rem;
32 | }
33 | }
--------------------------------------------------------------------------------
/cmd/go-go-mcp/cmds/client.go:
--------------------------------------------------------------------------------
1 | package cmds
2 |
3 | import (
4 | "github.com/go-go-golems/glazed/pkg/help"
5 | "github.com/go-go-golems/go-go-mcp/cmd/go-go-mcp/cmds/client"
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | var ClientCmd = &cobra.Command{
10 | Use: "client",
11 | Short: "MCP client functionality",
12 | Long: `Client commands for interacting with MCP servers`,
13 | }
14 |
15 | func InitClientCommand(helpSystem *help.HelpSystem) error {
16 | // Add client subcommands
17 | ClientCmd.AddCommand(client.ToolsCmd)
18 | ClientCmd.AddCommand(client.ResourcesCmd)
19 | ClientCmd.AddCommand(client.PromptsCmd)
20 |
21 | return nil
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/scholarly/common/logging.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "os"
5 | "time"
6 |
7 | "github.com/rs/zerolog"
8 | "github.com/rs/zerolog/log"
9 | )
10 |
11 | // SetupLogging configures zerolog based on debug mode
12 | func SetupLogging(debugMode bool) {
13 | zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
14 | zerolog.SetGlobalLevel(zerolog.InfoLevel)
15 | if debugMode {
16 | zerolog.SetGlobalLevel(zerolog.DebugLevel)
17 | // Use console writer for more readable debug logs
18 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
19 | }
20 | log.Debug().Msg("Debug mode enabled")
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/config/paths.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | // GetDefaultProfilesPath returns the default path for the profiles configuration file
9 | func GetDefaultProfilesPath() (string, error) {
10 | xdgConfigPath, err := os.UserConfigDir()
11 | if err != nil {
12 | return "", err
13 | }
14 | return filepath.Join(xdgConfigPath, "go-go-mcp", "profiles.yaml"), nil
15 | }
16 |
17 | // GetProfilesPath returns the profiles path from either the provided path or the default location
18 | func GetProfilesPath(configFile string) (string, error) {
19 | if configFile != "" {
20 | return configFile, nil
21 | }
22 | return GetDefaultProfilesPath()
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL Analysis"
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 | schedule:
9 | - cron: '0 0 * * 0' # Run weekly on Sunday at midnight
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | security-events: write
17 |
18 | steps:
19 | - name: Checkout repository
20 | uses: actions/checkout@v5
21 |
22 | - name: Initialize CodeQL
23 | uses: github/codeql-action/init@v3
24 | with:
25 | languages: go
26 |
27 | - name: Perform CodeQL Analysis
28 | uses: github/codeql-action/analyze@v3
--------------------------------------------------------------------------------
/pkg/auth/oidc/exports.go:
--------------------------------------------------------------------------------
1 | package oidc
2 |
3 | import "net/http"
4 |
5 | // Small exported adapters retained for flexibility
6 | func (s *Server) RoutesDiscovery(w http.ResponseWriter, r *http.Request) { s.oidcDiscovery(w, r) }
7 | func (s *Server) RoutesASMetadata(w http.ResponseWriter, r *http.Request) { s.asMetadata(w, r) }
8 | func (s *Server) Authorize(w http.ResponseWriter, r *http.Request) { s.authorize(w, r) }
9 | func (s *Server) Token(w http.ResponseWriter, r *http.Request) { s.token(w, r) }
10 | func (s *Server) Register(w http.ResponseWriter, r *http.Request) { s.register(w, r) }
11 | func (s *Server) Login(w http.ResponseWriter, r *http.Request) { s.login(w, r) }
12 |
--------------------------------------------------------------------------------
/examples/html-extract/pubmed.yaml:
--------------------------------------------------------------------------------
1 | name: pubmed-search
2 | short: Search PubMed and extract structured data
3 | long: |
4 | Search PubMed using a search term and extract structured data from the results page.
5 | Uses html-selector to parse the HTML and extract relevant information.
6 |
7 | flags:
8 | - name: search_term
9 | type: string
10 | help: PubMed search term
11 | required: true
12 | - name: max_pages
13 | type: int
14 | help: Maximum number of pages to scrape
15 | default: 1
16 |
17 | command:
18 | - html-selector
19 | - select
20 | - --urls
21 | - "https://pubmed.ncbi.nlm.nih.gov/?term={{ .Args.search_term }}"
22 | - --config
23 | - "/tmp/html-extraction-2025-01-26-19-54-54.yaml"
--------------------------------------------------------------------------------
/web/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "verbatimModuleSyntax": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "erasableSyntaxOnly": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/web/scholarly/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "verbatimModuleSyntax": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "erasableSyntaxOnly": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/cmd/go-go-mcp/cmds/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/go-go-golems/glazed/pkg/cli"
5 | "github.com/rs/zerolog/log"
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | // ServerCmd is the root command for server-related operations
10 | var ServerCmd = &cobra.Command{
11 | Use: "server",
12 | Short: "Server management commands",
13 | Long: `Commands for managing the MCP server, including starting the server and managing tools.`,
14 | }
15 |
16 | func init() {
17 | ServerCmd.AddCommand(ToolsCmd)
18 | startCmd, err := NewStartCommand()
19 | if err != nil {
20 | log.Fatal().Err(err).Msg("failed to create start command")
21 | }
22 | cobraStartCmd, err := cli.BuildCobraCommandFromBareCommand(startCmd)
23 | cobra.CheckErr(err)
24 | ServerCmd.AddCommand(cobraStartCmd)
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/registry.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | // Error represents a protocol error with a code and message
4 | type Error struct {
5 | Code int `json:"code"`
6 | Message string `json:"message"`
7 | }
8 |
9 | // Error implements the error interface
10 | func (e *Error) Error() string {
11 | return e.Message
12 | }
13 |
14 | // NewError creates a new Error with the given message and code
15 | func NewError(message string, code int) *Error {
16 | return &Error{
17 | Code: code,
18 | Message: message,
19 | }
20 | }
21 |
22 | // Common errors
23 | var (
24 | ErrPromptNotFound = NewError("prompt not found", -32000)
25 | ErrResourceNotFound = NewError("resource not found", -32001)
26 | ErrToolNotFound = NewError("tool not found", -32002)
27 | ErrNotImplemented = NewError("not implemented", -32003)
28 | )
29 |
--------------------------------------------------------------------------------
/web/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "erasableSyntaxOnly": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true
25 | },
26 | "include": ["src"]
27 | }
28 |
--------------------------------------------------------------------------------
/web/scholarly/src/App.css:
--------------------------------------------------------------------------------
1 | /* Custom styles for the Scholarly app */
2 |
3 | .scholarly-app {
4 | display: flex;
5 | flex-direction: column;
6 | min-height: 100vh;
7 | }
8 |
9 | .container {
10 | flex: 1;
11 | }
12 |
13 | .card {
14 | border-radius: 8px;
15 | overflow: hidden;
16 | transition: transform 0.2s, box-shadow 0.2s;
17 | }
18 |
19 | .card:hover {
20 | transform: translateY(-2px);
21 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
22 | }
23 |
24 | .list-group-item {
25 | border-left: none;
26 | border-right: none;
27 | transition: background-color 0.2s;
28 | }
29 |
30 | .list-group-item:hover {
31 | background-color: #f8f9fa;
32 | }
33 |
34 | .badge {
35 | font-weight: 500;
36 | }
37 |
38 | /* Responsive adjustments */
39 | @media (max-width: 992px) {
40 | .col-lg-8 {
41 | margin-top: 2rem;
42 | }
43 | }
--------------------------------------------------------------------------------
/web/scholarly/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": false,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "erasableSyntaxOnly": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true
25 | },
26 | "include": ["src"]
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | # this is copied over from goreleaser/goreleaser/.github/workflows/lint.yml
2 | name: golangci-lint
3 | on:
4 | push:
5 | tags:
6 | - v*
7 | branches:
8 | - main
9 | pull_request:
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | golangci:
15 | permissions:
16 | contents: read
17 | pull-requests: read
18 | name: lint
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v5
22 | with:
23 | fetch-depth: 0
24 | - run: git fetch --force --tags
25 | - uses: actions/setup-go@v5
26 | with:
27 | go-version: '>=1.19.5'
28 | cache: true
29 | - name: golangci-lint
30 | uses: golangci/golangci-lint-action@v8
31 | with:
32 | version: v2.1.0
33 | args: --timeout=5m
34 |
--------------------------------------------------------------------------------
/cmd/go-go-mcp/cmds/ui_cmd.go:
--------------------------------------------------------------------------------
1 | package cmds
2 |
3 | import (
4 | tea "github.com/charmbracelet/bubbletea"
5 | "github.com/go-go-golems/go-go-mcp/pkg/ui/tui"
6 | "github.com/rs/zerolog/log"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | func NewUICommand() *cobra.Command {
11 | cmd := &cobra.Command{
12 | Use: "ui",
13 | Short: "Interactive terminal UI for managing MCP servers",
14 | Long: `A terminal user interface for managing MCP server configurations for Cursor and Claude desktop.`,
15 | RunE: func(cmd *cobra.Command, args []string) error {
16 | log.Debug().Msg("Starting UI")
17 | p := tea.NewProgram(tui.NewModel(), tea.WithAltScreen())
18 | _, err := p.Run()
19 | if err != nil {
20 | log.Error().Err(err).Msg("Error running UI")
21 | }
22 | log.Debug().Msg("UI exited")
23 | return err
24 | },
25 | }
26 |
27 | return cmd
28 | }
29 |
--------------------------------------------------------------------------------
/web/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/examples/shell-commands/backup-db.yaml:
--------------------------------------------------------------------------------
1 | name: backup-db
2 | short: Backup a database to S3
3 | long: |
4 | Backup a database file to an S3 bucket using the AWS CLI.
5 | The command supports specifying the database file, target bucket,
6 | and AWS profile to use.
7 |
8 | flags:
9 | - name: database
10 | type: string
11 | help: Path to the database file to backup
12 | required: true
13 | - name: bucket
14 | type: string
15 | help: S3 bucket name
16 | required: true
17 | - name: profile
18 | type: string
19 | help: AWS profile to use
20 | default: default
21 |
22 | command:
23 | - aws
24 | - s3
25 | - cp
26 | - "{{ .Args.database }}"
27 | - "s3://{{ .Args.bucket }}/backups/{{ .Args.database | base }}"
28 |
29 | environment:
30 | AWS_PROFILE: "{{ .Args.profile }}"
31 |
32 | cwd: /var/lib/postgresql
33 | capture-stderr: true
--------------------------------------------------------------------------------
/web/scholarly/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/pkg/protocol/resources.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // Resource represents a resource exposed by the server
4 | type Resource struct {
5 | URI string `json:"uri"`
6 | Name string `json:"name"`
7 | Description string `json:"description,omitempty"`
8 | MimeType string `json:"mimeType,omitempty"`
9 | }
10 |
11 | // ResourceContent represents the content of a resource
12 | type ResourceContent struct {
13 | URI string `json:"uri"`
14 | MimeType string `json:"mimeType,omitempty"`
15 | Text string `json:"text,omitempty"`
16 | Blob string `json:"blob,omitempty"` // Base64 encoded
17 | }
18 |
19 | // ResourceTemplate represents a parameterized resource template
20 | type ResourceTemplate struct {
21 | URITemplate string `json:"uriTemplate"`
22 | Name string `json:"name"`
23 | Description string `json:"description,omitempty"`
24 | MimeType string `json:"mimeType,omitempty"`
25 | }
26 |
--------------------------------------------------------------------------------
/examples/smart-connect/list-notes-in-folder.yaml:
--------------------------------------------------------------------------------
1 | name: list-notes-in-folder
2 | short: List notes in folder
3 | long: |
4 | Provide the name of a folder for which to list notes. Lists all the notes in the provided folder.
5 |
6 | flags:
7 | - name: folder
8 | type: string
9 | help: The path of the folder for which to list notes.
10 | required: true
11 | - name: format
12 | type: choice
13 | help: Output format
14 | choices: [json, text]
15 | default: text
16 |
17 | shell-script: |
18 | #!/bin/bash
19 | set -euo pipefail
20 |
21 | # Build request body
22 | request_body='{"folder":{{.Args.folder | printf "%q"}}}'
23 |
24 | # Make API request
25 | response=$(curl -s -X POST \
26 | -H "Content-Type: application/json" \
27 | -H "Authorization: Bearer default" \
28 | -d "$request_body" \
29 | "http://localhost:37420/notes-v1/list-notes-in-folder")
30 |
31 | echo "$response"
--------------------------------------------------------------------------------
/web/scholarly/src/store/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import { setupListeners } from '@reduxjs/toolkit/query';
3 | import { scholarlyApi } from './scholarlyApi';
4 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | [scholarlyApi.reducerPath]: scholarlyApi.reducer,
9 | },
10 | middleware: (getDefaultMiddleware) =>
11 | getDefaultMiddleware().concat(scholarlyApi.middleware),
12 | });
13 |
14 | // Enable refetching on focus, reconnect, etc
15 | setupListeners(store.dispatch);
16 |
17 | // Typed versions of useDispatch and useSelector
18 | export type RootState = ReturnType;
19 | export type AppDispatch = typeof store.dispatch;
20 |
21 | export const useAppDispatch = () => useDispatch();
22 | export const useAppSelector: TypedUseSelectorHook = useSelector;
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arxiv-reranker-ui",
3 | "private": true,
4 | "version": "0.1.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^2.8.1",
14 | "axios": "^1.9.0",
15 | "bootstrap": "^5.3.6",
16 | "react": "^19.1.0",
17 | "react-dom": "^19.1.0",
18 | "react-redux": "^9.2.0"
19 | },
20 | "devDependencies": {
21 | "@eslint/js": "^9.25.0",
22 | "@types/react": "^19.1.2",
23 | "@types/react-dom": "^19.1.2",
24 | "@vitejs/plugin-react": "^4.4.1",
25 | "eslint": "^9.25.0",
26 | "eslint-plugin-react-hooks": "^5.2.0",
27 | "eslint-plugin-react-refresh": "^0.4.19",
28 | "globals": "^16.0.0",
29 | "typescript": "~5.8.3",
30 | "typescript-eslint": "^8.30.1",
31 | "vite": "^6.3.5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/smart-connect/read.yaml:
--------------------------------------------------------------------------------
1 | name: read
2 | short: Read files
3 | long: |
4 | Read files.
5 |
6 | flags:
7 | - name: paths
8 | type: stringList
9 | help: A list of file paths to read.
10 | - name: path
11 | type: string
12 | help: A single file path to read.
13 | - name: format
14 | type: choice
15 | help: Output format
16 | choices: [json, text]
17 | default: text
18 |
19 | shell-script: |
20 | #!/bin/bash
21 | set -euo pipefail
22 |
23 | # Build request body
24 | {{ if .Args.paths }}
25 | request_body='{"file_paths":[{{ range $i, $p := .Args.paths }}{{if $i}},{{end}}{{$p | printf "%q"}}{{end}}]}'
26 | {{ else }}
27 | request_body='{"file_path":{{.Args.path | printf "%q"}}}'
28 | {{ end }}
29 |
30 | # Make API request
31 | response=$(curl -s -X POST \
32 | -H "Content-Type: application/json" \
33 | -H "Authorization: Bearer default" \
34 | -d "$request_body" \
35 | "http://localhost:37420/notes-v1/read")
36 |
37 | echo "$response"
--------------------------------------------------------------------------------
/examples/flow-engineering/sightseeing-schedule.yaml:
--------------------------------------------------------------------------------
1 | name: sightseeing-schedule
2 | short: Generate a sightseeing schedule
3 | flags:
4 | - name: location
5 | type: string
6 | help: Location for sightseeing
7 | required: true
8 | - name: date
9 | type: date
10 | help: Date for the schedule
11 | required: true
12 | - name: preferences
13 | type: stringList
14 | help: List of preferences (e.g., museums, parks)
15 | default: []
16 | shell-script: |
17 | #!/bin/bash
18 | set -euo pipefail
19 |
20 | echo "Generating sightseeing schedule for {{ .Args.location }} on {{ .Args.date }}..."
21 | echo "Preferences: {{ .Args.preferences | join ", " }}"
22 |
23 | # Simulate schedule generation
24 | echo "9:00 AM - Visit the local museum"
25 | echo "11:00 AM - Walk in the central park"
26 | echo "1:00 PM - Lunch at a popular restaurant"
27 | echo "3:00 PM - Explore the historic district"
28 | echo "5:00 PM - Relax at a nearby cafe"
29 | echo "7:00 PM - Dinner at a local eatery"
--------------------------------------------------------------------------------
/pkg/protocol/utilities_test.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | )
7 |
8 | func TestNewCancellationNotification(t *testing.T) {
9 | requestID := "test-123"
10 | reason := "User requested cancellation"
11 |
12 | notif := NewCancellationNotification(requestID, reason)
13 |
14 | if notif.JSONRPC != "2.0" {
15 | t.Errorf("expected JSONRPC '2.0', got '%s'", notif.JSONRPC)
16 | }
17 |
18 | if notif.Method != "notifications/cancelled" {
19 | t.Errorf("expected method 'notifications/cancelled', got '%s'", notif.Method)
20 | }
21 |
22 | // Parse the params to verify they're correct
23 | var params CancellationParams
24 | if err := json.Unmarshal(notif.Params, ¶ms); err != nil {
25 | t.Errorf("failed to unmarshal params: %v", err)
26 | }
27 |
28 | if params.RequestID != requestID {
29 | t.Errorf("expected RequestID '%s', got '%s'", requestID, params.RequestID)
30 | }
31 |
32 | if params.Reason != reason {
33 | t.Errorf("expected Reason '%s', got '%s'", reason, params.Reason)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/shell-commands/process-logs.yaml:
--------------------------------------------------------------------------------
1 | name: process-logs
2 | short: Process application logs
3 | long: |
4 | Process application log files in a directory, extracting error messages
5 | and optionally filtering by severity level.
6 |
7 | flags:
8 | - name: log_dir
9 | type: string
10 | help: Directory containing log files
11 | required: true
12 | - name: output
13 | type: string
14 | help: Output file for collected errors
15 | default: errors.log
16 | - name: severity
17 | type: string
18 | help: Minimum error severity to include
19 | default: ERROR
20 | choices: [DEBUG, INFO, WARN, ERROR, FATAL]
21 |
22 | shell-script: |
23 | #!/bin/bash
24 | set -euo pipefail
25 |
26 | # Clear output file
27 | > {{ .Args.output }}
28 |
29 | # Process each log file
30 | for f in {{ .Args.log_dir }}/*.log; do
31 | echo "Processing $f..."
32 | grep "{{ .Args.severity }}" "$f" >> {{ .Args.output }} || true
33 | done
34 |
35 | echo "Done! Errors collected in {{ .Args.output }}"
36 |
37 | capture-stderr: true
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This file contains the configuration for golangci-lint
3 | # See https://golangci-lint.run/usage/configuration/ for reference
4 |
5 | # Defines the configuration version.
6 | # The only possible value is "2".
7 | version: "2"
8 |
9 | # Linters configuration
10 | linters:
11 | # Default set of linters.
12 | default: none
13 | # Enable specific linters
14 | enable:
15 | # defaults
16 | - errcheck
17 | - govet
18 | - ineffassign
19 | - staticcheck
20 | - unused
21 | # additional linters
22 | - exhaustive
23 | # - gochecknoglobals
24 | # - gochecknoinits
25 | - nonamedreturns
26 | - predeclared
27 | # Exclusions configuration
28 | exclusions:
29 | rules:
30 | - linters:
31 | - staticcheck
32 | text: 'SA1019: cli.CreateProcessorLegacy'
33 | settings:
34 | errcheck:
35 | exclude-functions:
36 | - (io.Closer).Close
37 | - fmt.Fprintf
38 | - fmt.Fprintln
39 |
40 | # Formatters configuration
41 | formatters:
42 | enable:
43 | - gofmt
44 |
--------------------------------------------------------------------------------
/web/scholarly/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scholarly-ui",
3 | "private": true,
4 | "version": "0.1.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^2.8.1",
14 | "@tanstack/react-query": "^5.75.7",
15 | "axios": "^1.9.0",
16 | "bootstrap": "^5.3.6",
17 | "formik": "^2.4.6",
18 | "react": "^19.1.0",
19 | "react-dom": "^19.1.0",
20 | "react-redux": "^9.2.0",
21 | "yup": "^1.6.1"
22 | },
23 | "devDependencies": {
24 | "@eslint/js": "^9.26.0",
25 | "@types/react": "^19.1.3",
26 | "@types/react-dom": "^19.1.3",
27 | "@vitejs/plugin-react": "^4.4.1",
28 | "eslint": "^9.26.0",
29 | "eslint-plugin-react-hooks": "^5.2.0",
30 | "eslint-plugin-react-refresh": "^0.4.20",
31 | "globals": "^16.1.0",
32 | "typescript": "^5.8.3",
33 | "typescript-eslint": "^8.32.0",
34 | "vite": "^6.3.5"
35 | }
36 | }
--------------------------------------------------------------------------------
/examples/sqlite/sql-open.yaml:
--------------------------------------------------------------------------------
1 | name: sql-open
2 | short: Open a SQLite database for subsequent operations
3 | long: |
4 | This tool stores a SQLite database filename in a temporary file (/tmp/db-name.txt)
5 | so that it can be used by subsequent sqlite commands without specifying the --db parameter.
6 |
7 | Examples:
8 | - Open a database: go-go-mcp run-command sql-open.yaml --db my.db
9 | - Then run queries: go-go-mcp run-command sqlite.yaml --sql "SELECT * FROM users"
10 |
11 | flags:
12 | - name: db
13 | type: string
14 | help: Path to the SQLite database file
15 | required: true
16 |
17 | shell-script: |
18 | #!/bin/bash
19 | set -euo pipefail
20 |
21 | # Store the database filename in a temporary file
22 | echo "{{ .Args.db }}" > /tmp/db-name.txt
23 |
24 | # Check if the database file exists
25 | if [[ ! -f "{{ .Args.db }}" ]]; then
26 | echo "Warning: Database file '{{ .Args.db }}' does not exist. It will be created when you execute a CREATE statement." >&2
27 | else
28 | echo "Database '{{ .Args.db }}' is now open for subsequent operations."
29 | fi
30 |
--------------------------------------------------------------------------------
/examples/smart-connect/read-notes.yaml:
--------------------------------------------------------------------------------
1 | name: read-notes
2 | short: Retrieve notes
3 | long: |
4 | Retrieves notes from a list of note paths. The exact file path of the note or else the closest partial match will be returned.
5 |
6 | flags:
7 | - name: paths
8 | type: stringList
9 | help: |
10 | The note paths of the notes to be retrieved. Retrieves notes from a list of note paths. The exact file path of the note or else the closest partial match will be returned.
11 | required: true
12 | - name: format
13 | type: choice
14 | help: Output format
15 | choices: [json, text]
16 | default: json
17 |
18 | shell-script: |
19 | #!/bin/bash
20 | set -euo pipefail
21 |
22 | # Build request body with only set arguments
23 | request_body='{"note_paths":[{{ range $i, $p := .Args.paths }}{{if $i}},{{end}}{{$p | printf "%q"}}{{end}}]}'
24 |
25 | # Make API request
26 | response=$(curl -s -X POST \
27 | -H "Content-Type: application/json" \
28 | -H "Authorization: Bearer default" \
29 | -d "$request_body" \
30 | "http://localhost:37420/notes-v1/read-notes")
31 |
32 | echo "$response"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Manuel Odendahl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pkg/tools/examples/echo.go:
--------------------------------------------------------------------------------
1 | package examples
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 |
7 | "github.com/go-go-golems/go-go-mcp/pkg/protocol"
8 | "github.com/go-go-golems/go-go-mcp/pkg/tools"
9 | "github.com/go-go-golems/go-go-mcp/pkg/tools/providers/tool-registry"
10 | )
11 |
12 | func RegisterEchoTool(registry *tool_registry.Registry) error {
13 | schemaJson := `{
14 | "type": "object",
15 | "properties": {
16 | "message": {
17 | "type": "string"
18 | }
19 | }
20 | }`
21 |
22 | tool, err := tools.NewToolImpl("echo", "Echo the input arguments", json.RawMessage(schemaJson))
23 | if err != nil {
24 | return err
25 | }
26 |
27 | registry.RegisterToolWithHandler(
28 | tool,
29 | func(ctx context.Context, tool tools.Tool, arguments map[string]interface{}) (*protocol.ToolResult, error) {
30 | message, ok := arguments["message"].(string)
31 | if !ok {
32 | return protocol.NewToolResult(
33 | protocol.WithError("message argument must be a string"),
34 | ), nil
35 | }
36 | return protocol.NewToolResult(
37 | protocol.WithText(message),
38 | ), nil
39 | })
40 |
41 | return nil
42 | }
43 |
--------------------------------------------------------------------------------
/web/scholarly/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { SearchParams, SearchResponse, SourcesResponse, HealthResponse } from '../types/scholarly';
3 |
4 | // Base API URL
5 | const API_BASE_URL = 'http://localhost:8080/api';
6 |
7 | // API client
8 | const apiClient = axios.create({
9 | baseURL: API_BASE_URL,
10 | headers: {
11 | 'Content-Type': 'application/json',
12 | },
13 | });
14 |
15 | // API functions
16 | export const searchPapers = async (params: SearchParams): Promise => {
17 | console.log('Searching with params:', params);
18 | try {
19 | const response = await apiClient.get('/search', { params });
20 | console.log('Search API response:', response.data);
21 | return response.data;
22 | } catch (error) {
23 | console.error('Search API error:', error);
24 | throw error;
25 | }
26 | };
27 |
28 | export const getSources = async (): Promise => {
29 | const response = await apiClient.get('/sources');
30 | return response.data;
31 | };
32 |
33 | export const getHealth = async (): Promise => {
34 | const response = await apiClient.get('/health');
35 | return response.data;
36 | };
--------------------------------------------------------------------------------
/examples/shell-commands/docker-comp.yaml:
--------------------------------------------------------------------------------
1 | name: docker-comp
2 | short: Run docker-compose with environment variables
3 | long: |
4 | Run docker-compose with templated environment variables and
5 | configurable compose file location.
6 |
7 | flags:
8 | - name: compose_file
9 | type: string
10 | help: Path to docker-compose.yml
11 | default: docker-compose.yml
12 | - name: project
13 | type: string
14 | help: Project name
15 | required: true
16 | - name: environment
17 | type: string
18 | help: Deployment environment
19 | default: development
20 | choices: [development, staging, production]
21 | - name: action
22 | type: string
23 | help: Docker compose action
24 | required: true
25 | choices: [up, down, restart, logs]
26 |
27 | command:
28 | - docker-compose
29 | - -f
30 | - "{{ .Args.compose_file }}"
31 | - -p
32 | - "{{ .Args.project }}"
33 | - "{{ .Args.action }}"
34 | - -d
35 |
36 | environment:
37 | COMPOSE_PROJECT_NAME: "{{ .Args.project }}"
38 | ENV: "{{ .Args.environment }}"
39 | DB_HOST: "db.{{ .Args.environment }}.local"
40 | REDIS_HOST: "redis.{{ .Args.environment }}.local"
41 |
42 | capture-stderr: true
--------------------------------------------------------------------------------
/python/reranker-server/README.md:
--------------------------------------------------------------------------------
1 | # ArXiv Paper Reranker Server
2 |
3 | This is a FastAPI server that provides API endpoints for reranking arXiv paper search results based on query relevance using cross-encoder models.
4 |
5 | ## Setup
6 |
7 | ### Prerequisites
8 |
9 | - Python 3.8+
10 | - PyTorch
11 | - sentence-transformers
12 | - FastAPI
13 | - uvicorn
14 |
15 | ### Installation
16 |
17 | 1. Install dependencies
18 | ```bash
19 | pip install sentence-transformers fastapi uvicorn pydantic torch
20 | ```
21 |
22 | 2. Run the server
23 | ```bash
24 | python arxiv_reranker_server.py
25 | ```
26 |
27 | The server will start on http://localhost:8000
28 |
29 | ## API Endpoints
30 |
31 | - `GET /`: Root endpoint with basic information
32 | - `POST /rerank`: Rerank ArXiv papers based on query relevance
33 | - `POST /rerank_json`: Rerank papers from standard ArXiv JSON format
34 | - `GET /models`: Get information about the currently loaded model
35 |
36 | ## Frontend
37 |
38 | A React frontend for this API is available in the `web/` directory. To run both the frontend and backend together, use the `start-dev.sh` script in the web directory:
39 |
40 | ```bash
41 | cd web
42 | ./start-dev.sh
43 | ```
--------------------------------------------------------------------------------
/examples/pages/welcome.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: "🎃 Welcome to Spookyville! 👻"
4 | id: main-title
5 |
6 | - text:
7 | content: "The night is dark, the moon is bright, welcome to our Halloween site! Explore our haunted pages and discover the spooky delights that await..."
8 | id: welcome-text
9 |
10 | - list:
11 | type: ul
12 | title: "Spooky Activities"
13 | items:
14 | - text:
15 | content: "🏚️ Haunted House Tour - Book your tour through our haunted mansion, if you dare..."
16 | - button:
17 | text: "Book Tour"
18 | type: danger
19 | onclick: "alert('Beware! The ghosts await...')"
20 | - text:
21 | content: "🎭 Costume Contest - Vote for the spookiest costume of the year!"
22 | - button:
23 | text: "Vote Now"
24 | type: primary
25 | onclick: "alert('Choose wisely...')"
26 | - text:
27 | content: "🎪 Halloween Party - Join us for a night of frightful fun!"
28 | - button:
29 | text: "RSVP"
30 | type: success
31 | onclick: "alert('We will be waiting for you...')"
--------------------------------------------------------------------------------
/examples/rag/recent-coaching-conversations.yaml:
--------------------------------------------------------------------------------
1 | name: recent-coaching-conversations
2 | short: List recent coaching conversations from the knowledge base
3 | long: |
4 | Retrieves a list of recent coaching conversations from the knowledge base.
5 | This tool should only be used in the context of discussing coaching topics
6 | or when needing to reference past coaching conversations for context.
7 |
8 | The tool provides:
9 | - Titles and summaries of recent coaching conversations
10 | - Timestamps of when the conversations occurred
11 | - Key topics discussed in each conversation
12 |
13 | Note: This tool should be called when you need to:
14 | - Reference recent coaching history
15 | - Build context for current coaching discussions
16 | - Check what topics have been recently discussed
17 | - Ensure continuity between coaching sessions
18 | shell-script: |
19 | #!/bin/bash
20 | set -euo pipefail
21 |
22 | cd /home/manuel/code/mento/go-go-mento
23 | source /home/manuel/code/mento/go-go-mento/.envrc
24 | cd /home/manuel/code/mento/go-go-mento/go
25 |
26 |
27 | go run ./cmd/mento-service rag \
28 | --job-config test/rag-jobs.yaml \
29 | --job-prefix local-testing \
30 | recent-documents
31 | capture-stderr: true
--------------------------------------------------------------------------------
/pkg/scholarly/mcp/register.go:
--------------------------------------------------------------------------------
1 | package mcp
2 |
3 | import (
4 | tool_registry "github.com/go-go-golems/go-go-mcp/pkg/tools/providers/tool-registry"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | // RegisterScholarlyTools registers all scholarly tools with the provided registry
9 | func RegisterScholarlyTools(registry *tool_registry.Registry) error {
10 | // Register all scholarly tools
11 | if err := registerSearchWorksTool(registry); err != nil {
12 | return errors.Wrap(err, "failed to register search_works tool")
13 | }
14 |
15 | if err := registerResolveDOITool(registry); err != nil {
16 | return errors.Wrap(err, "failed to register resolve_doi tool")
17 | }
18 |
19 | if err := registerSuggestKeywordsTool(registry); err != nil {
20 | return errors.Wrap(err, "failed to register suggest_keywords tool")
21 | }
22 |
23 | if err := registerGetMetricsTool(registry); err != nil {
24 | return errors.Wrap(err, "failed to register get_metrics tool")
25 | }
26 |
27 | if err := registerGetCitationsTool(registry); err != nil {
28 | return errors.Wrap(err, "failed to register get_citations tool")
29 | }
30 |
31 | if err := registerFindFullTextTool(registry); err != nil {
32 | return errors.Wrap(err, "failed to register find_full_text tool")
33 | }
34 |
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/ttmp/2025-04-27/01-add-cursor-mcp-config.md:
--------------------------------------------------------------------------------
1 | ### commands
2 |
3 | // This example demonstrated an MCP server using the stdio format
4 | // Cursor automatically runs this process for you
5 | // This uses a Node.js server, ran with `npx`
6 | {
7 | "mcpServers": {
8 | "server-name": {
9 | "command": "npx",
10 | "args": ["-y", "mcp-server"],
11 | "env": {
12 | "API_KEY": "value"
13 | }
14 | }
15 | }
16 | }
17 |
18 | ### sse
19 |
20 | // This example demonstrated an MCP server using the SSE format
21 | // The user should manually setup and run the server
22 | // This could be networked, to allow others to access it too
23 | {
24 | "mcpServers": {
25 | "server-name": {
26 | "url": "http://localhost:3000/sse",
27 | "env": {
28 | "API_KEY": "value"
29 | }
30 | }
31 | }
32 | }
33 |
34 | ### location
35 |
36 | Project Configuration
37 |
38 | For tools specific to a project, create a .cursor/mcp.json file in your project directory. This allows you to define MCP servers that are only available within that specific project.
39 | Global Configuration
40 |
41 | For tools that you want to use across all projects, create a \~/.cursor/mcp.json file in your home directory. This makes MCP servers available in all your Cursor workspaces.
--------------------------------------------------------------------------------
/web/README.md.new:
--------------------------------------------------------------------------------
1 | # ArXiv Paper Reranker
2 |
3 | This project provides a system for reranking arXiv paper search results based on their relevance to a search query using cross-encoder models.
4 |
5 | ## Project Structure
6 |
7 | - `python/reranker-server/`: FastAPI backend service that handles paper reranking
8 | - `web/`: React frontend for interacting with the reranking API
9 |
10 | ## Getting Started
11 |
12 | ### Backend Setup
13 |
14 | ```bash
15 | cd python/reranker-server
16 | pip install sentence-transformers fastapi uvicorn pydantic torch
17 | python arxiv_reranker_server.py
18 | ```
19 |
20 | The server will start on http://localhost:8000
21 |
22 | ### Frontend Setup
23 |
24 | ```bash
25 | cd web
26 | bun install
27 | bun run dev
28 | ```
29 |
30 | The frontend will start on http://localhost:5173
31 |
32 | ### Running Both Together
33 |
34 | Use the provided start script:
35 |
36 | ```bash
37 | cd web
38 | ./start-dev.sh
39 | ```
40 |
41 | ## Features
42 |
43 | - Cross-encoder based reranking of arXiv papers
44 | - Modern React frontend with Redux Toolkit
45 | - Support for different reranking models
46 | - API documentation available at http://localhost:8000/docs
47 |
48 | ## Requirements
49 |
50 | - Python 3.8+
51 | - Bun.js
52 | - PyTorch and sentence-transformers
53 | - FastAPI and uvicorn
--------------------------------------------------------------------------------
/web/scholarly/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Header: React.FC = () => {
4 | return (
5 |
15 | );
16 | };
17 |
18 | export default Header;
--------------------------------------------------------------------------------
/pkg/scholarly/common/http.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 |
8 | "github.com/rs/zerolog/log"
9 | )
10 |
11 | // HTTPResponse represents a standardized HTTP response
12 | type HTTPResponse struct {
13 | StatusCode int
14 | Body []byte
15 | Error error
16 | }
17 |
18 | // MakeHTTPRequest makes an HTTP request and returns a standardized response
19 | func MakeHTTPRequest(req *http.Request) *HTTPResponse {
20 | client := &http.Client{}
21 | resp, err := client.Do(req)
22 | if err != nil {
23 | return &HTTPResponse{
24 | Error: fmt.Errorf("error making request: %w", err),
25 | }
26 | }
27 | defer func() {
28 | if err := resp.Body.Close(); err != nil {
29 | log.Warn().Err(err).Msg("error closing response body")
30 | }
31 | }()
32 |
33 | body, err := io.ReadAll(resp.Body)
34 | if err != nil {
35 | return &HTTPResponse{
36 | Error: fmt.Errorf("error reading response body: %w", err),
37 | }
38 | }
39 |
40 | if resp.StatusCode != http.StatusOK {
41 | log.Warn().Int("status_code", resp.StatusCode).Str("url", req.URL.String()).Msg("HTTP request failed")
42 | log.Debug().Str("url", req.URL.String()).Bytes("response_body", body).Msg("HTTP request failed body")
43 | }
44 |
45 | return &HTTPResponse{
46 | StatusCode: resp.StatusCode,
47 | Body: body,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/web/scholarly/src/types/scholarly.ts:
--------------------------------------------------------------------------------
1 | // Types for the Scholarly API
2 |
3 | export interface ScholarlyPaper {
4 | id?: string;
5 | Title: string;
6 | Authors: string[];
7 | Abstract: string;
8 | Published: string;
9 | DOI: string;
10 | PDFURL: string;
11 | SourceURL: string;
12 | SourceName: string;
13 | OAStatus: string;
14 | License: string;
15 | FileSize: string;
16 | Citations: number;
17 | Type: string;
18 | JournalInfo: string;
19 | Metadata?: Record;
20 | reranked?: boolean;
21 | reranker_score?: number;
22 | original_index?: number;
23 | }
24 |
25 | export interface SearchParams {
26 | query?: string;
27 | sources?: string;
28 | limit?: number;
29 | author?: string;
30 | title?: string;
31 | category?: string;
32 | 'work-type'?: string;
33 | 'from-year'?: number;
34 | 'to-year'?: number;
35 | sort?: 'relevance' | 'newest' | 'oldest';
36 | 'open-access'?: string;
37 | mailto?: string;
38 | 'disable-rerank'?: boolean;
39 | }
40 |
41 | export interface SearchResponse {
42 | results: ScholarlyPaper[];
43 | query: string;
44 | count: number;
45 | sources: string[];
46 | }
47 |
48 | export interface SourcesResponse {
49 | sources: string[];
50 | }
51 |
52 | export interface HealthResponse {
53 | status: string;
54 | version: string;
55 | timestamp: string;
56 | }
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | # run only against tags
4 | tags:
5 | - '*'
6 |
7 | permissions:
8 | contents: write
9 | # packages: write
10 | # issues: write
11 |
12 |
13 | jobs:
14 | goreleaser:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v5
18 | with:
19 | fetch-depth: 0
20 | - run: git fetch --force --tags
21 | - uses: actions/setup-go@v5
22 | with:
23 | go-version: '>=1.19.5'
24 | cache: true
25 |
26 | - name: Import GPG key
27 | id: import_gpg
28 | uses: crazy-max/ghaction-import-gpg@v6
29 | with:
30 | gpg_private_key: ${{ secrets.GO_GO_GOLEMS_SIGN_KEY }}
31 | passphrase: ${{ secrets.GO_GO_GOLEMS_SIGN_PASSPHRASE }}
32 | fingerprint: "6EBE1DF0BDF48A1BBA381B5B79983EF218C6ED7E"
33 |
34 | - uses: goreleaser/goreleaser-action@v6
35 | with:
36 | distribution: goreleaser
37 | version: latest
38 | args: release --clean
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
42 | TAP_GITHUB_TOKEN: ${{ secrets.RELEASE_ACTION_PAT }}
43 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
44 | FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
45 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # ArXiv Paper Reranker UI
2 |
3 | A React frontend for interacting with the ArXiv Paper Reranker API. This application allows you to rerank ArXiv papers based on their relevance to a search query using cross-encoder models.
4 |
5 | ## Features
6 |
7 | - Submit ArXiv search results for reranking
8 | - View reranked papers with relevance scores
9 | - See information about the active reranking model
10 | - Access paper PDFs and source URLs
11 |
12 | ## Getting Started
13 |
14 | ### Prerequisites
15 |
16 | - Bun.js (latest version)
17 | - The ArXiv Reranker Server running on http://localhost:8000
18 |
19 | ### Installation
20 |
21 | 1. Install dependencies
22 | ```
23 | bun install
24 | ```
25 |
26 | 2. Run the development server
27 | ```
28 | bun run dev
29 | ```
30 |
31 | 3. Build for production
32 | ```
33 | bun run build
34 | ```
35 |
36 | ## Usage
37 |
38 | 1. Enter your search query
39 | 2. Paste ArXiv JSON results in the textarea (must include a "results" array with paper objects)
40 | 3. Click "Rerank Papers"
41 | 4. View the reranked results sorted by relevance score
42 |
43 | ## Technologies Used
44 |
45 | - React
46 | - Redux Toolkit (RTK Query)
47 | - Bootstrap
48 | - Axios
49 | - TypeScript
50 |
51 | ## API Connection
52 |
53 | This frontend connects to the ArXiv Paper Reranker API running on http://localhost:8000. Make sure the API is running before using this UI.
--------------------------------------------------------------------------------
/cmd/go-go-mcp/cmds/client/layers/client.go:
--------------------------------------------------------------------------------
1 | package layers
2 |
3 | import (
4 | "github.com/go-go-golems/glazed/pkg/cmds/layers"
5 | "github.com/go-go-golems/glazed/pkg/cmds/parameters"
6 | )
7 |
8 | type ClientSettings struct {
9 | Transport string `glazed.parameter:"transport"`
10 | Server string `glazed.parameter:"server"`
11 | Command []string `glazed.parameter:"command"`
12 | }
13 |
14 | const ClientLayerSlug = "mcp-client"
15 |
16 | func NewClientParameterLayer() (layers.ParameterLayer, error) {
17 | return layers.NewParameterLayer(ClientLayerSlug, "MCP Client Settings",
18 | layers.WithParameterDefinitions(
19 | parameters.NewParameterDefinition(
20 | "transport",
21 | parameters.ParameterTypeString,
22 | parameters.WithHelp("Transport type (command or sse)"),
23 | parameters.WithDefault("command"),
24 | ),
25 | parameters.NewParameterDefinition(
26 | "server",
27 | parameters.ParameterTypeString,
28 | parameters.WithHelp("Server URL for SSE transport"),
29 | parameters.WithDefault("http://localhost:3001"),
30 | ),
31 | parameters.NewParameterDefinition(
32 | "command",
33 | parameters.ParameterTypeStringList,
34 | parameters.WithHelp("Command and arguments for command transport (starts go-go-mcp in stdio mode per default)"),
35 | parameters.WithDefault([]string{"mcp", "server", "start", "--transport", "stdio"}),
36 | ),
37 | ),
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/examples/shell-commands/git-sync.yaml:
--------------------------------------------------------------------------------
1 | name: git-sync
2 | short: Sync git repositories
3 | long: |
4 | Synchronize multiple git repositories by pulling latest changes
5 | and optionally pushing local changes.
6 |
7 | flags:
8 | - name: repos_dir
9 | type: string
10 | help: Base directory containing git repositories
11 | required: true
12 | - name: branch
13 | type: string
14 | help: Branch to sync
15 | default: main
16 | - name: push
17 | type: bool
18 | help: Also push local changes
19 | default: false
20 |
21 | shell-script: |
22 | #!/bin/bash
23 | set -euo pipefail
24 |
25 | # Function to sync a single repository
26 | sync_repo() {
27 | local repo=$1
28 | echo "Syncing $repo..."
29 | cd "$repo"
30 |
31 | # Fetch and checkout branch
32 | git fetch origin
33 | git checkout {{ .Args.branch }}
34 |
35 | # Pull changes
36 | git pull origin {{ .Args.branch }}
37 |
38 | # Push if requested
39 | if [[ "{{ .Args.push }}" == "true" ]]; then
40 | echo "Pushing changes..."
41 | git push origin {{ .Args.branch }}
42 | fi
43 | }
44 |
45 | # Find and process all git repositories
46 | find {{ .Args.repos_dir }} -type d -name ".git" | while read -r gitdir; do
47 | repo_dir=$(dirname "$gitdir")
48 | sync_repo "$repo_dir"
49 | done
50 |
51 | environment:
52 | GIT_TERMINAL_PROMPT: "0"
53 |
54 | capture-stderr: true
--------------------------------------------------------------------------------
/web/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/web/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/scholarly/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ttmp/2025-08-23/03-implementation-progress.md:
--------------------------------------------------------------------------------
1 | ### Implementation Progress: Porting `jesus` to `go-mcp`
2 |
3 | #### Purpose and scope
4 | Track concrete implementation steps as they land, with code references and next tasks.
5 |
6 | ---
7 |
8 | ### Reasoning walkthrough
9 | - Current code already registers `executeJS` via `embeddable.AddMCPCommand` in `jesus/pkg/mcp/server.go` and spins up JS/admin servers on startup hook.
10 | - Next work focuses on adding more tools and creating a `JSEngineService` adapter without changing transports.
11 |
12 | ---
13 |
14 | ### Completed
15 | - Initial inventory and migration plan documents.
16 |
17 | ---
18 |
19 | ### In progress
20 | - Designing `JSEngineService` adapter and identifying engine methods for list routes/logs.
21 |
22 | Potential locations in engine:
23 | - Handler registry: `jesus/pkg/engine/engine.go` has `handlers map[string]map[string]*HandlerInfo` and accessors used in router; we can add read-only getters to enumerate.
24 | - Request logs: `engine.Engine` exposes `GetRequestLogger()` used in admin; add method to tail logs or expose last N entries.
25 |
26 | ---
27 |
28 | ### Next steps
29 | - Add read-only getters to engine for routes and logs if missing.
30 | - Implement `JSEngineService` and wire additional MCP tools in `jesus/pkg/mcp/server.go`.
31 | - Validate via `go-go-mcp mcp start --transport stdio` and tool `list-tools`.
32 |
33 | ---
34 |
35 | Requested output path: `go-go-mcp/ttmp/2025-08-23/03-implementation-progress.md`
36 |
--------------------------------------------------------------------------------
/examples/smart-connect/insert-into-note.yaml:
--------------------------------------------------------------------------------
1 | name: insert-into-note
2 | short: Insert text into a note
3 | long: |
4 | Insert text into a note at a specific location, either before or after a heading.
5 |
6 | flags:
7 | - name: path
8 | type: string
9 | help: Path of the note to modify
10 | required: true
11 | - name: insert
12 | type: string
13 | help: Text to insert
14 | required: true
15 | - name: before_heading
16 | type: string
17 | help: Insert before this heading
18 | - name: after_heading
19 | type: string
20 | help: Insert after this heading
21 | - name: format
22 | type: choice
23 | help: Output format
24 | choices: [json, text]
25 | default: json
26 |
27 | shell-script: |
28 | #!/bin/bash
29 | set -euo pipefail
30 |
31 | # Construct request body
32 | request_body='{"note_path":{{.Args.path | printf "%q"}},"insert":{{.Args.insert | printf "%q"}}'
33 | {{ if .Args.before_heading }}
34 | request_body="$request_body,\"insert_before_heading\":{{.Args.before_heading | printf "%q"}}"
35 | {{ else if .Args.after_heading }}
36 | request_body="$request_body,\"insert_after_heading\":{{.Args.after_heading | printf "%q"}}"
37 | {{ end }}
38 | request_body="$request_body}"
39 |
40 | # Make API request
41 | response=$(curl -s -X POST \
42 | -H "Content-Type: application/json" \
43 | -H "Authorization: Bearer default" \
44 | -d "$request_body" \
45 | "http://localhost:37420/insert-into-note")
46 |
47 | echo "$response"
--------------------------------------------------------------------------------
/examples/rag/search-coaching-conversations.yaml:
--------------------------------------------------------------------------------
1 | name: search-coaching-conversations
2 | short: Search through past coaching conversations
3 | long: |
4 | Performs a semantic search through the history of coaching conversations
5 | to find relevant discussions and insights. This tool should only be used
6 | in the context of discussing coaching topics or when specific coaching-related
7 | information needs to be retrieved.
8 |
9 | The tool provides:
10 | - Relevant excerpts from past coaching conversations
11 | - Context around the matched content
12 | - Temporal information about when the conversations occurred
13 |
14 | Note: This tool should be called when you need to:
15 | - Find specific advice given in past coaching sessions
16 | - Research how similar topics were handled previously
17 | - Build upon past coaching insights
18 | - Connect current discussions with historical context
19 |
20 | The search is semantic, meaning it will find relevant content even if
21 | the exact words are not matched.
22 | flags:
23 | - name: query
24 | type: string
25 | help: The coaching-related topic or question to search for in past conversations
26 | required: true
27 | shell-script: |
28 | #!/bin/bash
29 | set -euo pipefail
30 |
31 | cd /home/manuel/code/mento/go-go-mento
32 | source /home/manuel/code/mento/go-go-mento/.envrc
33 | cd /home/manuel/code/mento/go-go-mento/go
34 |
35 | escuse-me examples search-summaries-embeddings --query "{{ .Args.query }}" --output yaml
36 | capture-stderr: true
--------------------------------------------------------------------------------
/examples/smart-connect/keywords-search.yaml:
--------------------------------------------------------------------------------
1 | name: keywords-search
2 | short: Search for keywords in files
3 | long: |
4 | Search for keywords in files and return matching file paths.
5 |
6 | flags:
7 | - name: keywords
8 | type: stringList
9 | help: A list of keywords to match against.
10 | required: true
11 | - name: limit
12 | type: int
13 | help: "Max results (default: 20)"
14 | default: 20
15 | - name: format
16 | type: choice
17 | help: Output format
18 | choices: [json, text]
19 | default: text
20 |
21 | shell-script: |
22 | #!/bin/bash
23 | set -euo pipefail
24 |
25 | # Build request body using go templating
26 | request_body='{'
27 | {{ if .Args.keywords }}
28 | request_body="$request_body\"keywords\":["
29 | first=true
30 | for keyword in "${keywords[@]}"; do
31 | if [ "$first" = true ]; then
32 | first=false
33 | else
34 | request_body="$request_body,"
35 | fi
36 | request_body="$request_body$(printf '%q' "$keyword")"
37 | done
38 | request_body="$request_body]"
39 | {{ end }}
40 | {{ if .Args.limit }}
41 | {{ if .Args.keywords }}
42 | request_body="$request_body,"
43 | {{ end }}
44 | request_body="$request_body\"limit\":{{ .Args.limit }}"
45 | {{ end }}
46 | request_body="$request_body}"
47 |
48 | # Make API request
49 | response=$(curl -s -X POST \
50 | -H "Content-Type: application/json" \
51 | -H "Authorization: Bearer default" \
52 | -d "$request_body" \
53 | "http://localhost:37420/notes-v1/keywords-search")
54 |
55 | echo "$response"
--------------------------------------------------------------------------------
/examples/smart-connect/create-note.yaml:
--------------------------------------------------------------------------------
1 | name: create-note
2 | short: Create a new note
3 | long: |
4 | Create a new note with the given path and content.
5 |
6 | flags:
7 | - name: path
8 | type: string
9 | help: |
10 | The unique identifier for the note. Create 'Context' folders (e.g., 'History', 'Recipes') -> Add 'Subject' notes in each Context (e.g., 'History' -> 'WWII', 'Renaissance'; 'Recipes' -> 'Chicken Soup', 'Apple Pie'). Context = Category, Subject = Detailed notes within category.
11 | required: true
12 | - name: content
13 | type: string
14 | help: The content of the note in markdown format.
15 | default: ""
16 | - name: content_file
17 | type: file
18 | help: File containing note content
19 | - name: format
20 | type: choice
21 | help: Output format
22 | choices: [json, text]
23 | default: json
24 |
25 | shell-script: |
26 | #!/bin/bash
27 | set -euo pipefail
28 |
29 | # Get content from file if specified
30 | content="{{ .Args.content }}"
31 | {{ if .Args.content_file }}
32 | content=$(cat "{{ .Args.content_file }}")
33 | {{ end }}
34 |
35 | # Build request body
36 | request_body="{\"note_path\":\"{{.Args.path | printf "%q"}}\",\"note_content\":\""
37 | request_body+=$content
38 | request_body+="\"}"
39 | echo "$request_body"
40 |
41 | # Make API request
42 | response=$(curl -s -X POST \
43 | -H "Content-Type: application/json" \
44 | -H "Authorization: Bearer default" \
45 | -d "$request_body" \
46 | "http://localhost:37420/notes-v1/create-note")
47 |
48 | echo "$response"
--------------------------------------------------------------------------------
/examples/pages/haunted-house.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: "🏚️ Book Your Haunted House Tour"
4 | id: tour-title
5 |
6 | - text:
7 | content: "Dare to explore our haunted mansion? Fill out the form below to reserve your spot in our terrifying tour. Remember, the ghosts are waiting..."
8 | id: tour-description
9 |
10 | - form:
11 | id: tour-form
12 | components:
13 | - input:
14 | type: text
15 | placeholder: "Your Name (if you survive...)"
16 | required: true
17 | id: name-input
18 | - input:
19 | type: email
20 | placeholder: "Email (for ghost communications)"
21 | required: true
22 | id: email-input
23 | - input:
24 | type: date
25 | placeholder: "Select your doom date"
26 | required: true
27 | id: date-input
28 | - input:
29 | type: number
30 | placeholder: "Number of brave souls"
31 | required: true
32 | id: guests-input
33 | - checkbox:
34 | label: "I acknowledge that the spirits may possess my soul 👻"
35 | required: true
36 | id: waiver-checkbox
37 | - textarea:
38 | placeholder: "Any last words or special requests?"
39 | rows: 3
40 | id: comments-input
41 | - button:
42 | text: "Book If You Dare"
43 | type: danger
44 | onclick: "alert('Your fate is sealed...')"
45 | id: submit-btn
--------------------------------------------------------------------------------
/web/src/services/api.ts:
--------------------------------------------------------------------------------
1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
2 |
3 | export interface ArxivPaper {
4 | Title: string;
5 | Authors: string[];
6 | Abstract: string;
7 | Published: string;
8 | DOI: string;
9 | PDFURL: string;
10 | SourceURL: string;
11 | SourceName: string;
12 | OAStatus: string;
13 | License: string;
14 | FileSize: string;
15 | Citations: number;
16 | Type: string;
17 | JournalInfo: string;
18 | Metadata: Record;
19 | }
20 |
21 | export interface ScoredPaper extends ArxivPaper {
22 | score: number;
23 | }
24 |
25 | export interface RerankerRequest {
26 | query: string;
27 | results: ArxivPaper[];
28 | top_n?: number;
29 | }
30 |
31 | export interface RerankerResponse {
32 | query: string;
33 | reranked_results: ScoredPaper[];
34 | }
35 |
36 | export interface ModelsResponse {
37 | current_model: string;
38 | description: string;
39 | alternatives: string[];
40 | }
41 |
42 | // Define the API service
43 | export const arxivApi = createApi({
44 | reducerPath: 'arxivApi',
45 | baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8000' }),
46 | endpoints: (builder) => ({
47 | rerank: builder.mutation({
48 | query: (request) => ({
49 | url: '/rerank',
50 | method: 'POST',
51 | body: request,
52 | }),
53 | }),
54 | getModels: builder.query({
55 | query: () => '/models',
56 | }),
57 | }),
58 | });
59 |
60 | export const { useRerankMutation, useGetModelsQuery } = arxivApi;
--------------------------------------------------------------------------------
/examples/html-extract/html-extraction.yaml:
--------------------------------------------------------------------------------
1 | name: html-extraction
2 | short: Extract data from HTML using selectors
3 | long: |
4 | Extract data from HTML documents using CSS or XPath selectors.
5 | IMPORTANT: Run get-html-extraction-tutorial first to understand how to create selector configurations.
6 | NEVER RUN THIS TOOL WITHOUT HAVING READ THE TUTORIAL FIRST.
7 |
8 | flags:
9 | - name: config
10 | type: string
11 | help: YAML configuration string containing selectors and extraction rules
12 | required: true
13 | - name: urls
14 | type: stringList
15 | help: List of URLs to extract data from
16 | required: true
17 | - name: show_context
18 | type: bool
19 | help: Show context around matches
20 | default: false
21 | - name: show_path
22 | type: bool
23 | help: Show element path in results
24 | default: false
25 |
26 | shell-script: |
27 | #!/bin/bash
28 | set -euo pipefail
29 |
30 | # Create temporary config file
31 | CONFIG_FILE=$(mktemp)
32 | trap 'rm -f "$CONFIG_FILE"' EXIT
33 |
34 | # Generate config file with heredoc to handle special characters better
35 | cat > "$CONFIG_FILE" << 'EOL'
36 | {{ .Args.config }}
37 | EOL
38 |
39 | # Log config file with timestamp for replay
40 | LOG_FILE="/tmp/html-extraction-$(date '+%Y-%m-%d-%H-%M-%S').yaml"
41 | cp "$CONFIG_FILE" "$LOG_FILE"
42 |
43 | html-selector select \
44 | --urls {{ range .Args.urls }}{{ . }} {{ end }} \
45 | --config "$CONFIG_FILE" \
46 | {{ if .Args.show_context }}--show-context{{ end }} \
47 | {{ if .Args.show_path }}--show-path{{ end }} \
--------------------------------------------------------------------------------
/web/scholarly/README.md:
--------------------------------------------------------------------------------
1 | # Scholarly Search UI
2 |
3 | A React frontend for the Scholarly API, providing a user-friendly interface to search for academic papers across multiple sources including ArXiv, Crossref, and OpenAlex.
4 |
5 | ## Features
6 |
7 | - Search for papers using various criteria (query text, author, title, etc.)
8 | - Filter results by source, year range, and open access status
9 | - Sort results by relevance, newest, or oldest
10 | - View paper details including abstracts, authors, and publication info
11 | - Access PDFs and source links directly from the interface
12 |
13 | ## Getting Started
14 |
15 | ### Prerequisites
16 |
17 | - Bun.js (latest version)
18 | - The Scholarly API server running (typically on http://localhost:8080)
19 |
20 | ### Installation
21 |
22 | 1. Install dependencies
23 | ```bash
24 | bun install
25 | ```
26 |
27 | 2. Run the development server
28 | ```bash
29 | bun run dev
30 | ```
31 |
32 | 3. Build for production
33 | ```bash
34 | bun run build
35 | ```
36 |
37 | ## Deployment
38 |
39 | The build process creates a `dist` directory with static files that can be served by any static file server. The Scholarly API's serve command is configured to serve these files.
40 |
41 | ## API Connection
42 |
43 | This frontend connects to the Scholarly API running at the URL specified in `src/services/api.ts` (default: http://localhost:8080/api). Update this URL if your API is running on a different host or port.
44 |
45 | ## Technologies Used
46 |
47 | - React
48 | - TypeScript
49 | - TanStack Query (React Query)
50 | - Formik & Yup
51 | - Bootstrap
52 | - Axios
--------------------------------------------------------------------------------
/web/scholarly/src/store/scholarlyApi.ts:
--------------------------------------------------------------------------------
1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
2 | import type { SearchParams, ScholarlyPaper } from '../types/scholarly';
3 |
4 | // Define response types
5 | export interface SearchResponse {
6 | results: ScholarlyPaper[];
7 | query: string;
8 | count: number;
9 | sources: string[];
10 | }
11 |
12 | export interface SourcesResponse {
13 | sources: string[];
14 | }
15 |
16 | export interface HealthResponse {
17 | status: string;
18 | version: string;
19 | timestamp: string;
20 | }
21 |
22 | // Define the API
23 | export const scholarlyApi = createApi({
24 | reducerPath: 'scholarlyApi',
25 | baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8080/api' }),
26 | endpoints: (builder) => ({
27 | search: builder.query({
28 | query: (params) => ({
29 | url: '/search',
30 | params,
31 | }),
32 | transformResponse: (response: SearchResponse) => {
33 | console.log('RTK Query received response:', response);
34 | return response;
35 | },
36 | }),
37 | getSources: builder.query({
38 | query: () => '/sources',
39 | transformResponse: (response: SourcesResponse) => response.sources,
40 | }),
41 | getHealth: builder.query({
42 | query: () => '/health',
43 | }),
44 | }),
45 | });
46 |
47 | // Export the auto-generated hooks
48 | export const {
49 | useSearchQuery,
50 | useLazySearchQuery,
51 | useGetSourcesQuery,
52 | useGetHealthQuery
53 | } = scholarlyApi;
--------------------------------------------------------------------------------
/examples/smart-connect/append-note.yaml:
--------------------------------------------------------------------------------
1 | name: append-note
2 | short: Append content to a note
3 | long: |
4 | Append content to a note with the given path.
5 |
6 | flags:
7 | - name: path
8 | type: string
9 | help: |
10 | The unique identifier for the note. Create 'Context' folders (e.g., 'History', 'Recipes') -> Add 'Subject' notes in each Context (e.g., 'History' -> 'WWII', 'Renaissance'; 'Recipes' -> 'Chicken Soup', 'Apple Pie'). Context = Category, Subject = Detailed notes within category.
11 | required: true
12 | - name: content
13 | type: string
14 | help: The content to append to the note in markdown format.
15 | default: ""
16 | - name: content_file
17 | type: file
18 | help: File containing content to append
19 | - name: format
20 | type: choice
21 | help: Output format
22 | choices: [json, text]
23 | default: json
24 |
25 | shell-script: |
26 | #!/bin/bash
27 | set -euo pipefail
28 | # Build request body using go templating
29 | request_body='{"note_path":{{.Args.path | printf "%q"}}'
30 |
31 | # Get content from file if specified
32 | {{ if .Args.content_file }}
33 | content=$(cat "{{ .Args.content_file }}")
34 | {{ else }}
35 | content="{{ .Args.content }}"
36 | {{ end }}
37 | request_body="$request_body,\"append_content\":$(printf '%q' "$content")}"
38 |
39 | # Make API request
40 | response=$(curl -s -X POST \
41 | -H "Content-Type: application/json" \
42 | -H "Authorization: Bearer default" \
43 | -d "$request_body" \
44 | "http://localhost:37420/notes-v1/append-note")
45 |
46 | echo "$response"
--------------------------------------------------------------------------------
/examples/pages/costume-contest.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: "🎭 Halloween Costume Contest"
4 | id: contest-title
5 |
6 | - text:
7 | content: "Vote for the spookiest, most creative costume of the year! Each contestant has put their heart (and maybe some actual organs) into their costumes."
8 | id: contest-description
9 |
10 | - list:
11 | type: ul
12 | title: "Contest Entries"
13 | items:
14 | - text:
15 | content: "🧟♂️ Zombie Businessman - Bob from accounting really committed to the role - he hasn't showered in weeks!"
16 | - button:
17 | text: "Vote for Bob"
18 | type: primary
19 | onclick: "alert('Your vote for Zombie Bob has been counted!')"
20 | - text:
21 | content: "🧙♀️ Techno Witch - Sarah's costume combines traditional witchcraft with RGB lighting - truly spellbinding!"
22 | - button:
23 | text: "Vote for Sarah"
24 | type: primary
25 | onclick: "alert('Your vote for Techno Witch has been counted!')"
26 | - text:
27 | content: "👻 Ghost in the Machine - Mike dressed up as a haunted computer - blue screen of death included!"
28 | - button:
29 | text: "Vote for Mike"
30 | type: primary
31 | onclick: "alert('Your vote for Ghost Mike has been counted!')"
32 |
33 | - text:
34 | content: "Voting closes at midnight on Halloween. The winner gets a one-way ticket to the shadow realm! (Just kidding, they get a gift card)"
35 | id: voting-rules-text
36 |
37 |
--------------------------------------------------------------------------------
/examples/pages/todo.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: What would you like to tackle next?
4 | id: priority-title
5 |
6 | - text:
7 | content: I see you have several items that need attention. Let's organize them by priority and type.
8 | id: context-text
9 |
10 | - list:
11 | type: ul
12 | title: "Current Tasks"
13 | items:
14 | - text:
15 | content: "Review Dependencies"
16 | - button:
17 | text: Review Nokogiri Update (#316)
18 | type: secondary
19 | - text:
20 | content: "Calendar Integration"
21 | - button:
22 | text: Review Calendar CLI PR (#315)
23 | type: primary
24 | - text:
25 | content: "Coaching Features"
26 | - button:
27 | text: Review Coaching Chat PR (#311)
28 | type: primary
29 |
30 | - form:
31 | id: task-input-form
32 | components:
33 | - title:
34 | content: Add New Task
35 | - input:
36 | type: text
37 | placeholder: What else needs to be done?
38 | id: new-task
39 | required: true
40 | - textarea:
41 | placeholder: Add any important context or notes
42 | rows: 3
43 | id: task-notes
44 | - checkbox:
45 | label: High Priority
46 | id: priority-flag
47 | - button:
48 | text: Add Task
49 | type: success
50 |
51 | - text:
52 | content: Would you like me to help you analyze any of these items in detail?
53 | id: help-offer
--------------------------------------------------------------------------------
/examples/smart-connect/replace-in-note.yaml:
--------------------------------------------------------------------------------
1 | name: replace-in-note
2 | short: Replace text in a note
3 | long: |
4 | Replace text in a note using either a simple find/replace or a range replacement.
5 |
6 | flags:
7 | - name: path
8 | type: string
9 | help: Path of the note to modify
10 | required: true
11 | - name: find
12 | type: string
13 | help: Text to find and replace
14 | - name: find_start
15 | type: string
16 | help: Start of text range to replace
17 | - name: find_end
18 | type: string
19 | help: End of text range to replace
20 | - name: replace_with
21 | type: string
22 | help: Text to replace with
23 | required: true
24 | - name: format
25 | type: choice
26 | help: Output format
27 | choices: [json, text]
28 | default: json
29 |
30 | shell-script: |
31 | #!/bin/bash
32 | set -euo pipefail
33 |
34 | # Construct request body based on find type
35 | request_body='{"note_path":{{.Args.path | printf "%q"}}'
36 | {{ if .Args.find }}
37 | request_body="$request_body,\"find\":{{.Args.find | printf "%q"}},\"replace_with\":{{.Args.replace_with | printf "%q"}}"
38 | {{ else }}
39 | request_body="$request_body,\"find_start\":{{.Args.find_start | printf "%q"}},\"find_end\":{{.Args.find_end | printf "%q"}},\"replace_with\":{{.Args.replace_with | printf "%q"}}"
40 | {{ end }}
41 | request_body="$request_body}"
42 |
43 | # Make API request
44 | response=$(curl -s -X POST \
45 | -H "Content-Type: application/json" \
46 | -H "Authorization: Bearer default" \
47 | -d "$request_body" \
48 | "http://localhost:37420/replace-in-note")
49 |
50 | echo "$response"
51 |
--------------------------------------------------------------------------------
/pkg/tools/registry.go:
--------------------------------------------------------------------------------
1 | package tools
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-go-golems/go-go-mcp/pkg"
7 | "github.com/go-go-golems/go-go-mcp/pkg/protocol"
8 | )
9 |
10 | // CombinedProvider implements the ToolProvider interface by combining multiple providers
11 | type CombinedProvider struct {
12 | providers []pkg.ToolProvider
13 | }
14 |
15 | // ListTools implements the ToolProvider interface by combining results from all providers
16 | func (cp *CombinedProvider) ListTools(ctx context.Context, cursor string) ([]protocol.Tool, string, error) {
17 | var tools []protocol.Tool
18 | for _, provider := range cp.providers {
19 | providerTools, _, err := provider.ListTools(ctx, "")
20 | if err != nil {
21 | return nil, "", err
22 | }
23 | tools = append(tools, providerTools...)
24 | }
25 | return tools, "", nil
26 | }
27 |
28 | // CallTool implements the ToolProvider interface by trying each provider in order
29 | func (cp *CombinedProvider) CallTool(ctx context.Context, name string, arguments map[string]interface{}) (*protocol.ToolResult, error) {
30 | for _, provider := range cp.providers {
31 | result, err := provider.CallTool(ctx, name, arguments)
32 | if err == nil {
33 | return result, nil
34 | }
35 | // If tool not found, try the next provider
36 | if err == pkg.ErrToolNotFound {
37 | continue
38 | }
39 | // For other errors, return them
40 | return nil, err
41 | }
42 | // If no provider can handle the tool, return not found
43 | return nil, pkg.ErrToolNotFound
44 | }
45 |
46 | // CombineProviders creates a new provider that combines multiple providers
47 | func CombineProviders(providers ...pkg.ToolProvider) pkg.ToolProvider {
48 | return &CombinedProvider{
49 | providers: providers,
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/rag/diary-append.yaml:
--------------------------------------------------------------------------------
1 | name: diary-append
2 | short: Append a diary entry with timestamp to /tmp/DIARY
3 | long: |
4 | This command appends a diary entry to /tmp/DIARY, automatically formatting it as markdown
5 | with the current date as a heading. It's useful for maintaining a simple timestamped log
6 | or diary of events, thoughts, or notes.
7 |
8 | INPUT: A message to append to the diary
9 | OUTPUT: The message is appended to /tmp/DIARY with proper markdown formatting
10 |
11 | The command:
12 | - Creates /tmp/DIARY if it doesn't exist
13 | - Adds current date as a level 2 heading
14 | - Formats the message as markdown text
15 | - Adds two newlines after each entry for readability
16 |
17 | Common use cases:
18 | 1. Log a quick thought: --message "Need to remember to check the logs"
19 | 2. Record a decision: --message "Decided to use PostgreSQL for the project"
20 | 3. Keep track of tasks: --message "- [ ] Review PR #123\n- [ ] Update documentation"
21 |
22 | flags:
23 | - name: message
24 | type: string
25 | help: |
26 | The message to append to the diary.
27 | Can include markdown formatting.
28 | Will be added under a timestamp heading.
29 | Example: --message "Important meeting notes:\n- Discussed roadmap\n- Set Q2 goals"
30 | required: true
31 |
32 | shell-script: |
33 | #!/bin/bash
34 | set -euo pipefail
35 |
36 | # Create diary file if it doesn't exist
37 | touch /tmp/DIARY
38 |
39 | # Format current date as markdown heading
40 | date_heading="## $(date '+%Y-%m-%d %H:%M:%S')"
41 |
42 | # Append entry to diary
43 | {
44 | echo -e "\n$date_heading\n"
45 | echo "{{ .Args.message }}"
46 | echo -e "\n"
47 | } >> /tmp/DIARY
48 |
49 | echo "Entry added to /tmp/DIARY"
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: gifs
2 |
3 | all: gifs
4 |
5 | VERSION ?= $(shell svu)
6 | COMMIT ?= $(shell git rev-parse --short HEAD)
7 | DIRTY ?= $(shell git diff --quiet || echo "dirty")
8 | LDFLAGS=-ldflags "-X main.version=$(VERSION)-$(COMMIT)-$(DIRTY)"
9 |
10 | TAPES=$(shell ls doc/vhs/*tape)
11 | gifs: $(TAPES)
12 | for i in $(TAPES); do vhs < $$i; done
13 |
14 | ghcr-login:
15 | op read "$(CR_PAT)" | docker login ghcr.io -u wesen --password-stdin
16 |
17 |
18 | docker-lint:
19 | docker run --rm -v $(shell pwd):/app -w /app golangci/golangci-lint:v2.1.0 golangci-lint run -v
20 |
21 | lint:
22 | golangci-lint run -v
23 |
24 | lintmax:
25 | golangci-lint run -v --max-same-issues=100
26 |
27 | gosec:
28 | go install github.com/securego/gosec/v2/cmd/gosec@latest
29 | gosec -exclude=G101,G304,G301,G306,G204 -exclude-dir=.history ./...
30 |
31 | govulncheck:
32 | go install golang.org/x/vuln/cmd/govulncheck@latest
33 | govulncheck ./...
34 |
35 | test:
36 | go test ./...
37 |
38 | build:
39 | go generate ./...
40 | go build ./...
41 |
42 | goreleaser:
43 | goreleaser release --skip=sign --snapshot --clean
44 |
45 | tag-major:
46 | git tag $(shell svu major)
47 |
48 | tag-minor:
49 | git tag $(shell svu minor)
50 |
51 | tag-patch:
52 | git tag $(shell svu patch)
53 |
54 | release:
55 | git push --tags
56 | GOPROXY=proxy.golang.org go list -m github.com/go-go-golems/go-go-mcp@$(shell svu current)
57 |
58 | bump-glazed:
59 | go get github.com/go-go-golems/glazed@latest
60 | go get github.com/go-go-golems/clay@latest
61 | go get github.com/go-go-golems/geppetto@latest
62 | go get github.com/go-go-golems/parka@latest
63 | go mod tidy
64 |
65 | mcp_BINARY=$(shell which mcp)
66 | install:
67 | go build -o ./dist/mcp ./cmd/go-go-mcp && \
68 | cp ./dist/mcp $(mcp_BINARY)
69 |
--------------------------------------------------------------------------------
/examples/shell-commands/diary-append.yaml:
--------------------------------------------------------------------------------
1 | name: diary-append
2 | short: Append a diary entry with timestamp to /tmp/DIARY
3 | long: |
4 | This command appends a diary entry to /tmp/DIARY, automatically formatting it as markdown
5 | with the current date as a heading. It's useful for maintaining a simple timestamped log
6 | or diary of events, thoughts, or notes.
7 |
8 | INPUT: A message to append to the diary
9 | OUTPUT: The message is appended to /tmp/DIARY with proper markdown formatting
10 |
11 | The command:
12 | - Creates /tmp/DIARY if it doesn't exist
13 | - Adds current date as a level 2 heading
14 | - Formats the message as markdown text
15 | - Adds two newlines after each entry for readability
16 |
17 | Common use cases:
18 | 1. Log a quick thought: --message "Need to remember to check the logs"
19 | 2. Record a decision: --message "Decided to use PostgreSQL for the project"
20 | 3. Keep track of tasks: --message "- [ ] Review PR #123\n- [ ] Update documentation"
21 |
22 | flags:
23 | - name: message
24 | type: string
25 | help: |
26 | The message to append to the diary.
27 | Can include markdown formatting.
28 | Will be added under a timestamp heading.
29 | Example: --message "Important meeting notes:\n- Discussed roadmap\n- Set Q2 goals"
30 | required: true
31 |
32 | shell-script: |
33 | #!/bin/bash
34 | set -euo pipefail
35 |
36 | # Create diary file if it doesn't exist
37 | touch /tmp/DIARY
38 |
39 | # Format current date as markdown heading
40 | date_heading="## $(date '+%Y-%m-%d %H:%M:%S')"
41 |
42 | # Append entry to diary
43 | {
44 | echo -e "\n$date_heading\n"
45 | echo "{{ .Args.message }}"
46 | echo -e "\n"
47 | } >> /tmp/DIARY
48 |
49 | echo "Entry added to /tmp/DIARY"
--------------------------------------------------------------------------------
/examples/ui/example-form.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: Dinosaur Facts Subscription
4 | id: dino-title
5 |
6 | - text:
7 | content: Subscribe to receive amazing dinosaur facts directly to your inbox!
8 | id: dino-description
9 |
10 | - form:
11 | id: dino-subscription-form
12 | components:
13 | - input:
14 | type: text
15 | placeholder: Your Name
16 | required: true
17 | id: name-input
18 |
19 | - input:
20 | type: email
21 | placeholder: Your Email
22 | required: true
23 | id: email-input
24 |
25 | - list:
26 | type: ul
27 | title: Favorite Dinosaur Types
28 | items:
29 | - checkbox:
30 | label: Theropods (T-Rex, Velociraptor)
31 | id: theropod-checkbox
32 |
33 | - checkbox:
34 | label: Sauropods (Brachiosaurus, Diplodocus)
35 | id: sauropod-checkbox
36 |
37 | - checkbox:
38 | label: Ceratopsians (Triceratops)
39 | id: ceratopsian-checkbox
40 |
41 | - checkbox:
42 | label: Stegosaurs (Stegosaurus)
43 | id: stegosaur-checkbox
44 |
45 | - textarea:
46 | placeholder: Tell us about your favorite dinosaur
47 | rows: 3
48 | id: favorite-textarea
49 |
50 | - checkbox:
51 | label: Subscribe to weekly newsletter
52 | id: newsletter-checkbox
53 | checked: true
54 |
55 | - button:
56 | text: Subscribe Now
57 | type: success
58 | id: subscribe-btn
--------------------------------------------------------------------------------
/pkg/protocol/prompts.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // PromptArgument represents an argument for a prompt
4 | type PromptArgument struct {
5 | Name string `json:"name"`
6 | Description string `json:"description,omitempty"`
7 | Required bool `json:"required"`
8 | }
9 |
10 | // Prompt represents a prompt template
11 | type Prompt struct {
12 | Name string `json:"name"`
13 | Description string `json:"description,omitempty"`
14 | Arguments []PromptArgument `json:"arguments,omitempty"`
15 | }
16 |
17 | // PromptMessage represents a message in a prompt
18 | type PromptMessage struct {
19 | Role string `json:"role"` // "user" or "assistant"
20 | Content PromptContent `json:"content"`
21 | }
22 |
23 | // PromptContent represents different types of content in a prompt message
24 | type PromptContent struct {
25 | Type string `json:"type"` // "text", "image", or "resource"
26 | Text string `json:"text,omitempty"` // For text content
27 | Data string `json:"data,omitempty"` // Base64 encoded for image content
28 | MimeType string `json:"mimeType,omitempty"`
29 | Resource *ResourceContent `json:"resource,omitempty"` // For resource content
30 | }
31 |
32 | type ListPromptsResult struct {
33 | Prompts []Prompt `json:"prompts"`
34 | NextCursor string `json:"nextCursor"`
35 | }
36 |
37 | type ListResourcesResult struct {
38 | Resources []Resource `json:"resources"`
39 | NextCursor string `json:"nextCursor"`
40 | }
41 |
42 | type ListToolsResult struct {
43 | Tools []Tool `json:"tools"`
44 | NextCursor string `json:"nextCursor"`
45 | }
46 |
47 | type PromptResult struct {
48 | Description string `json:"description"`
49 | Messages []PromptMessage `json:"messages"`
50 | }
51 |
52 | type ResourceResult struct {
53 | Contents []ResourceContent `json:"contents"`
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/providers.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-go-golems/go-go-mcp/pkg/protocol"
7 | )
8 |
9 | // PromptProvider defines the interface for serving prompts
10 | type PromptProvider interface {
11 | // ListPrompts returns a list of available prompts with optional pagination
12 | ListPrompts(ctx context.Context, cursor string) ([]protocol.Prompt, string, error)
13 |
14 | // GetPrompt retrieves a specific prompt with the given arguments
15 | GetPrompt(ctx context.Context, name string, arguments map[string]string) (*protocol.PromptMessage, error)
16 | }
17 |
18 | // ResourceProvider defines the interface for serving resources
19 | type ResourceProvider interface {
20 | // ListResources returns a list of available resources with optional pagination
21 | ListResources(ctx context.Context, cursor string) ([]protocol.Resource, string, error)
22 |
23 | // ReadResource retrieves the contents of a specific resource
24 | ReadResource(ctx context.Context, uri string) ([]protocol.ResourceContent, error)
25 |
26 | // ListResourceTemplates returns a list of available resource templates
27 | ListResourceTemplates(ctx context.Context) ([]protocol.ResourceTemplate, error)
28 |
29 | // SubscribeToResource registers for notifications about resource changes
30 | // Returns a channel that will receive notifications and a cleanup function
31 | SubscribeToResource(ctx context.Context, uri string) (chan struct{}, func(), error)
32 | }
33 |
34 | // ToolProvider defines the interface for serving tools
35 | type ToolProvider interface {
36 | // ListTools returns a list of available tools with optional pagination
37 | ListTools(ctx context.Context, cursor string) ([]protocol.Tool, string, error)
38 |
39 | // CallTool invokes a specific tool with the given arguments
40 | CallTool(ctx context.Context, name string, arguments map[string]interface{}) (*protocol.ToolResult, error)
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/protocol/sampling.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // Message represents a message in a conversation
4 | type Message struct {
5 | Role string `json:"role"` // "user" or "assistant"
6 | Content MessageContent `json:"content"`
7 | Model string `json:"model,omitempty"`
8 | }
9 |
10 | // MessageContent represents different types of content in a message
11 | type MessageContent struct {
12 | Type string `json:"type"` // "text" or "image"
13 | Text string `json:"text,omitempty"` // For text content
14 | Data string `json:"data,omitempty"` // Base64 encoded for image content
15 | MimeType string `json:"mimeType,omitempty"`
16 | }
17 |
18 | // ModelPreferences represents preferences for model selection
19 | type ModelPreferences struct {
20 | Hints []ModelHint `json:"hints,omitempty"`
21 | CostPriority float64 `json:"costPriority,omitempty"`
22 | SpeedPriority float64 `json:"speedPriority,omitempty"`
23 | IntelligencePriority float64 `json:"intelligencePriority,omitempty"`
24 | }
25 |
26 | // ModelHint represents a suggested model name
27 | type ModelHint struct {
28 | Name string `json:"name"`
29 | }
30 |
31 | // CreateMessageRequest represents a request to create a message
32 | type CreateMessageRequest struct {
33 | Messages []Message `json:"messages"`
34 | ModelPreferences ModelPreferences `json:"modelPreferences,omitempty"`
35 | SystemPrompt string `json:"systemPrompt,omitempty"`
36 | MaxTokens int `json:"maxTokens,omitempty"`
37 | }
38 |
39 | // CreateMessageResponse represents the response to a create message request
40 | type CreateMessageResponse struct {
41 | Role string `json:"role"`
42 | Content MessageContent `json:"content"`
43 | Model string `json:"model,omitempty"`
44 | StopReason string `json:"stopReason,omitempty"`
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/protocol/base_test.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | )
7 |
8 | func TestBatchRequestValidation(t *testing.T) {
9 | tests := []struct {
10 | name string
11 | batch BatchRequest
12 | shouldErr bool
13 | }{
14 | {
15 | name: "empty batch should error",
16 | batch: BatchRequest{},
17 | shouldErr: true,
18 | },
19 | {
20 | name: "valid batch should pass",
21 | batch: BatchRequest{
22 | {JSONRPC: "2.0", Method: "test1", ID: json.RawMessage(`"1"`)},
23 | {JSONRPC: "2.0", Method: "test2", ID: json.RawMessage(`"2"`)},
24 | },
25 | shouldErr: false,
26 | },
27 | {
28 | name: "invalid JSONRPC version should error",
29 | batch: BatchRequest{
30 | {JSONRPC: "1.0", Method: "test1", ID: json.RawMessage(`"1"`)},
31 | },
32 | shouldErr: true,
33 | },
34 | }
35 |
36 | for _, tt := range tests {
37 | t.Run(tt.name, func(t *testing.T) {
38 | err := tt.batch.Validate()
39 | if tt.shouldErr && err == nil {
40 | t.Errorf("expected error but got none")
41 | }
42 | if !tt.shouldErr && err != nil {
43 | t.Errorf("unexpected error: %v", err)
44 | }
45 | })
46 | }
47 | }
48 |
49 | func TestBatchRequestGetByID(t *testing.T) {
50 | batch := BatchRequest{
51 | {JSONRPC: "2.0", Method: "test1", ID: json.RawMessage(`"1"`)},
52 | {JSONRPC: "2.0", Method: "test2", ID: json.RawMessage(`"2"`)},
53 | }
54 |
55 | // Test finding existing request
56 | req := batch.GetRequestByID(json.RawMessage(`"1"`))
57 | if req == nil {
58 | t.Error("expected to find request with ID '1'")
59 | }
60 | if req != nil && req.Method != "test1" {
61 | t.Errorf("expected method 'test1', got '%s'", req.Method)
62 | }
63 |
64 | // Test not finding non-existent request
65 | req = batch.GetRequestByID(json.RawMessage(`"3"`))
66 | if req != nil {
67 | t.Error("expected not to find request with ID '3'")
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/examples/pages/dino-facts.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: dino-facts-title
4 | content: "🦕 Fascinating Dinosaur Facts"
5 |
6 | - text:
7 | id: intro-text
8 | content: "Journey back millions of years and discover amazing facts about these prehistoric creatures!"
9 |
10 | - list:
11 | type: ul
12 | items:
13 | - list:
14 | type: ul
15 | title: "T-Rex Facts"
16 | items:
17 | - text:
18 | content: "Could run up to 20 mph"
19 | - text:
20 | content: "Had powerful jaws with 60 teeth"
21 | - text:
22 | content: "Lived during the late Cretaceous period"
23 | - button:
24 | id: cretaceous-info
25 | text: "Learn More"
26 | type: secondary
27 |
28 | - list:
29 | type: ul
30 | title: "Velociraptor Facts"
31 | items:
32 | - text:
33 | content: "Was about the size of a turkey"
34 | - text:
35 | content: "Had feathers"
36 | - text:
37 | content: "Was a pack hunter"
38 | - button:
39 | id: pack-hunting-info
40 | text: "See Video"
41 | type: primary
42 |
43 | - form:
44 | id: fact-subscription
45 | components:
46 | - title:
47 | content: "Get Daily Dino Facts!"
48 | - input:
49 | id: email-input
50 | type: email
51 | placeholder: "Enter your email"
52 | required: true
53 | - checkbox:
54 | id: newsletter-check
55 | label: "Also subscribe to our paleontology newsletter"
56 | - button:
57 | id: subscribe-btn
58 | text: "Subscribe"
59 | type: success
--------------------------------------------------------------------------------
/examples/pages/halloween-party.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: "🎪 Halloween Party RSVP"
4 | id: party-title
5 |
6 | - text:
7 | content: "Join us for the spookiest party of the year! There will be treats, tricks, and maybe a few uninvited ghostly guests..."
8 | id: party-description
9 |
10 | - form:
11 | id: rsvp-form
12 | components:
13 | - input:
14 | type: text
15 | placeholder: "Your Name (mortal or otherwise)"
16 | required: true
17 | id: name-input
18 | - input:
19 | type: email
20 | placeholder: "Email Address"
21 | required: true
22 | id: email-input
23 | - input:
24 | type: text
25 | placeholder: "Your Costume Plan"
26 | required: true
27 | id: costume-input
28 | - checkbox:
29 | label: "I'll bring a spooky snack to share 🍪"
30 | id: snack-checkbox
31 | - checkbox:
32 | label: "I'm bringing a plus-one (living guests only, please) 👥"
33 | id: guest-checkbox
34 | - list:
35 | type: ul
36 | items:
37 | - text:
38 | content: "🎵 Music preferences:"
39 | - checkbox:
40 | label: "Monster Mash"
41 | id: music-1
42 | - checkbox:
43 | label: "Thriller"
44 | id: music-2
45 | - checkbox:
46 | label: "Ghostbusters Theme"
47 | id: music-3
48 | - textarea:
49 | placeholder: "Any dietary restrictions? (Blood type preferences?)"
50 | rows: 2
51 | id: dietary-input
52 | - button:
53 | text: "RSVP to Party"
54 | type: success
55 | onclick: "alert('Your soul is on the guest list!')"
56 | id: submit-btn
--------------------------------------------------------------------------------
/examples/pages/trick-or-treat.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: "🎃 Trick-or-Treat Checklist"
4 | id: checklist-title
5 |
6 | - text:
7 | content: "Make sure you're prepared for a night of spooky fun! Check off these essential items before heading out."
8 | id: checklist-description
9 |
10 | - form:
11 | id: checklist-form
12 | components:
13 | - title:
14 | content: "Essential Items:"
15 | - checkbox:
16 | label: "🎭 Costume (properly fitted for maximum candy acquisition)"
17 | id: costume-check
18 | - checkbox:
19 | label: "🎒 Candy collection bag/bucket (reinforced for heavy loads)"
20 | id: bag-check
21 | - checkbox:
22 | label: "🔦 Flashlight (to spot friendly ghosts)"
23 | id: flashlight-check
24 | - checkbox:
25 | label: "📱 Phone (for emergency ghost selfies)"
26 | id: phone-check
27 | - checkbox:
28 | label: "🧥 Warm layer (ghosts make the air chilly)"
29 | id: warmth-check
30 |
31 | - title:
32 | content: "Safety Measures:"
33 | - checkbox:
34 | label: "👥 Buddy system arranged"
35 | id: buddy-check
36 | - checkbox:
37 | label: "🗺️ Route planned (avoiding known werewolf territories)"
38 | id: route-check
39 | - checkbox:
40 | label: "⌚ Watch/time-keeping device (to return before turning into a pumpkin)"
41 | id: time-check
42 | - checkbox:
43 | label: "🔋 All devices charged (for documenting paranormal activities)"
44 | id: battery-check
45 |
46 | - button:
47 | text: "Ready to Haunt!"
48 | type: primary
49 | onclick: "alert('Happy haunting! Remember: the ghosts are more scared of you than you are of them... maybe.')"
50 | id: ready-btn
--------------------------------------------------------------------------------
/examples/pages/cow/cow-facts.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: cow-facts-title
4 | content: "🐄 Fascinating Cow Facts"
5 |
6 | - text:
7 | id: intro-text
8 | content: "Discover amazing facts about these gentle giants that have been our companions for thousands of years!"
9 |
10 | - list:
11 | type: ul
12 | items:
13 | - list:
14 | type: ul
15 | title: "General Facts"
16 | items:
17 | - text:
18 | content: "Cows can sleep while standing up"
19 | - text:
20 | content: "They have 40,000 jaw movements a day"
21 | - text:
22 | content: "Each cow has a unique pattern of spots"
23 | - button:
24 | id: pattern-info
25 | text: "Learn More"
26 | type: secondary
27 |
28 | - list:
29 | type: ul
30 | title: "Social Facts"
31 | items:
32 | - text:
33 | content: "Cows have best friends"
34 | - text:
35 | content: "They form strong social bonds"
36 | - text:
37 | content: "They can recognize over 100 other cows"
38 | - button:
39 | id: social-info
40 | text: "See Research"
41 | type: primary
42 |
43 | - form:
44 | id: fact-subscription
45 | components:
46 | - title:
47 | content: "Get Daily Cow Facts!"
48 | - input:
49 | id: email-input
50 | type: email
51 | placeholder: "Enter your email"
52 | required: true
53 | - checkbox:
54 | id: newsletter-check
55 | label: "Also subscribe to our farming newsletter"
56 | - button:
57 | id: subscribe-btn
58 | text: "Subscribe"
59 | type: success
--------------------------------------------------------------------------------
/pkg/events/types.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | )
8 |
9 | // UIEvent represents an event in the UI system that can trigger updates
10 | type UIEvent struct {
11 | Type string `json:"type"` // e.g. "component-update", "page-reload"
12 | PageID string `json:"pageId"` // Which page is being updated
13 | Component interface{} `json:"component"` // The updated component data
14 | RequestID string `json:"requestId,omitempty"` // Optional request ID for tracking user actions
15 | }
16 |
17 | // EventManager defines the interface for managing UI events
18 | type EventManager interface {
19 | // Subscribe returns a channel that receives events for the specified page
20 | Subscribe(ctx context.Context, pageID string) (<-chan UIEvent, error)
21 | // Publish sends an event for the specified page
22 | Publish(pageID string, event UIEvent) error
23 | // Close cleans up resources used by the event manager
24 | Close() error
25 | }
26 |
27 | // NewPageReloadEvent creates a new event for reloading a page
28 | func NewPageReloadEvent(pageID string, pageDef interface{}) UIEvent {
29 | return UIEvent{
30 | Type: "page-reload",
31 | PageID: pageID,
32 | Component: map[string]interface{}{"data": pageDef},
33 | }
34 | }
35 |
36 | // Validate checks if the event is valid
37 | func (e UIEvent) Validate() error {
38 | if e.Type == "" {
39 | return fmt.Errorf("event type cannot be empty")
40 | }
41 | if e.PageID == "" {
42 | return fmt.Errorf("page ID cannot be empty")
43 | }
44 | return nil
45 | }
46 |
47 | // ToJSON converts the event to JSON bytes
48 | func (e UIEvent) ToJSON() ([]byte, error) {
49 | return json.Marshal(e)
50 | }
51 |
52 | // FromJSON creates an event from JSON bytes
53 | func FromJSON(data []byte) (UIEvent, error) {
54 | var event UIEvent
55 | if err := json.Unmarshal(data, &event); err != nil {
56 | return UIEvent{}, fmt.Errorf("failed to unmarshal event: %w", err)
57 | }
58 | return event, nil
59 | }
60 |
--------------------------------------------------------------------------------
/web/src/components/ModelInfo.tsx:
--------------------------------------------------------------------------------
1 | import { useGetModelsQuery } from '../services/api';
2 |
3 | const ModelInfo = () => {
4 | const { data, error, isLoading } = useGetModelsQuery();
5 |
6 | if (isLoading) {
7 | return (
8 |
9 |
10 |
Model Information
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | if (error) {
22 | return (
23 |
24 |
25 |
Model Information
26 |
27 |
28 |
29 | Failed to load model information.
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | if (!data) {
37 | return null;
38 | }
39 |
40 | return (
41 |
42 |
43 |
Model Information
44 |
45 |
46 |
Current Model
47 |
{data.current_model}
48 |
49 |
Description
50 |
{data.description}
51 |
52 | {data.alternatives.length > 0 && (
53 | <>
54 |
Alternative Models
55 |
56 | {data.alternatives.map((model) => (
57 | - {model}
58 | ))}
59 |
60 | >
61 | )}
62 |
63 |
64 | );
65 | };
66 |
67 | export default ModelInfo;
--------------------------------------------------------------------------------
/pkg/protocol/utilities.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import "encoding/json"
4 |
5 | // CancellationParams represents parameters for a cancellation notification
6 | type CancellationParams struct {
7 | RequestID string `json:"requestId"`
8 | Reason string `json:"reason,omitempty"`
9 | }
10 |
11 | // NewCancellationNotification creates a new cancellation notification
12 | func NewCancellationNotification(requestID, reason string) *Notification {
13 | params, _ := json.Marshal(CancellationParams{
14 | RequestID: requestID,
15 | Reason: reason,
16 | })
17 | return &Notification{
18 | JSONRPC: "2.0",
19 | Method: "notifications/cancelled",
20 | Params: params,
21 | }
22 | }
23 |
24 | // ProgressParams represents parameters for a progress notification
25 | type ProgressParams struct {
26 | ProgressToken string `json:"progressToken"`
27 | Progress float64 `json:"progress"`
28 | Total float64 `json:"total,omitempty"`
29 | }
30 |
31 | // CompletionReference represents what is being completed
32 | type CompletionReference struct {
33 | Type string `json:"type"` // "ref/prompt" or "ref/resource"
34 | Name string `json:"name,omitempty"`
35 | URI string `json:"uri,omitempty"`
36 | }
37 |
38 | // CompletionArgument represents the argument being completed
39 | type CompletionArgument struct {
40 | Name string `json:"name"`
41 | Value string `json:"value"`
42 | }
43 |
44 | // CompletionResult represents completion suggestions
45 | type CompletionResult struct {
46 | Values []string `json:"values"`
47 | Total *int `json:"total,omitempty"`
48 | HasMore bool `json:"hasMore"`
49 | }
50 |
51 | // LogMessage represents a log message notification
52 | type LogMessage struct {
53 | Level string `json:"level"`
54 | Logger string `json:"logger,omitempty"`
55 | Data map[string]any `json:"data,omitempty"`
56 | }
57 |
58 | // Root represents a filesystem root exposed by the client
59 | type Root struct {
60 | URI string `json:"uri"` // Must be a file:// URI
61 | Name string `json:"name,omitempty"`
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/cmds/loader.go:
--------------------------------------------------------------------------------
1 | package cmds
2 |
3 | import (
4 | "io"
5 | "io/fs"
6 | "os"
7 | "strings"
8 |
9 | glazed_cmds "github.com/go-go-golems/glazed/pkg/cmds"
10 | "github.com/go-go-golems/glazed/pkg/cmds/alias"
11 | "github.com/go-go-golems/glazed/pkg/cmds/loaders"
12 | "github.com/pkg/errors"
13 | )
14 |
15 | type ShellCommandLoader struct{}
16 |
17 | var _ loaders.CommandLoader = &ShellCommandLoader{}
18 |
19 | func (l *ShellCommandLoader) LoadCommands(
20 | fs_ fs.FS,
21 | filePath string,
22 | options []glazed_cmds.CommandDescriptionOption,
23 | aliasOptions []alias.Option,
24 | ) ([]glazed_cmds.Command, error) {
25 | f, err := fs_.Open(filePath)
26 | if err != nil {
27 | return nil, errors.Wrapf(err, "could not open file %s", filePath)
28 | }
29 | defer func() {
30 | if closeErr := f.Close(); closeErr != nil {
31 | if err == nil {
32 | err = errors.Wrapf(closeErr, "could not close file %s", filePath)
33 | }
34 | }
35 | }()
36 |
37 | data, err := io.ReadAll(f)
38 | if err != nil {
39 | return nil, errors.Wrapf(err, "could not read file %s", filePath)
40 | }
41 |
42 | cmd, err := LoadShellCommandFromYAML(data)
43 | if err != nil {
44 | return nil, errors.Wrapf(err, "could not load shell command from file %s", filePath)
45 | }
46 |
47 | // Apply any additional options
48 | for _, opt := range options {
49 | opt(cmd.CommandDescription)
50 | }
51 |
52 | return []glazed_cmds.Command{cmd}, nil
53 | }
54 |
55 | func (l *ShellCommandLoader) GetFileExtensions() []string {
56 | return []string{".yaml", ".yml"}
57 | }
58 |
59 | func (l *ShellCommandLoader) GetName() string {
60 | return "shell"
61 | }
62 |
63 | func (l *ShellCommandLoader) IsFileSupported(f fs.FS, fileName string) bool {
64 | return strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml")
65 | }
66 |
67 | func LoadShellCommand(path string) (*ShellCommand, error) {
68 | data, err := os.ReadFile(path)
69 | if err != nil {
70 | return nil, errors.Wrapf(err, "could not read file %s", path)
71 | }
72 |
73 | return LoadShellCommandFromYAML(data)
74 | }
75 |
--------------------------------------------------------------------------------
/examples/pages/build-a-dino.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: build-dino-title
4 | content: "🦖 Build Your Own Dinosaur"
5 |
6 | - text:
7 | id: build-intro
8 | content: "Create your perfect prehistoric companion! Mix and match features to design your unique dinosaur."
9 |
10 | - form:
11 | id: dino-builder
12 | components:
13 | - input:
14 | id: dino-name
15 | type: text
16 | placeholder: "Name your dinosaur"
17 | required: true
18 |
19 | - list:
20 | type: ul
21 | title: "Physical Characteristics"
22 | items:
23 | - input:
24 | id: dino-height
25 | type: number
26 | placeholder: "Height (meters)"
27 | - input:
28 | id: dino-weight
29 | type: number
30 | placeholder: "Weight (tons)"
31 |
32 | - textarea:
33 | id: dino-description
34 | placeholder: "Describe your dinosaur's personality..."
35 | rows: 4
36 | cols: 50
37 |
38 | - checkbox:
39 | id: carnivore-check
40 | label: "Is it a carnivore?"
41 |
42 | - checkbox:
43 | id: feathers-check
44 | label: "Does it have feathers?"
45 |
46 | - text:
47 | content: "Select special abilities:"
48 |
49 | - list:
50 | type: ul
51 | title: "Special Abilities"
52 | items:
53 | - checkbox:
54 | id: ability-swim
55 | label: "Swimming"
56 | - checkbox:
57 | id: ability-fly
58 | label: "Flying"
59 | - checkbox:
60 | id: ability-camouflage
61 | label: "Camouflage"
62 |
63 | - button:
64 | id: create-dino-btn
65 | text: "Create Dinosaur"
66 | type: primary
--------------------------------------------------------------------------------
/pkg/mcp/types/types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // CommonServer is a unified structure to represent server configurations
4 | // from different sources (Cursor, Claude Desktop, etc.) within the UI.
5 | type CommonServer struct {
6 | Name string // Name identifier for the server
7 | Command string // Command to execute for the server (stdio)
8 | Args []string // Arguments for the command (stdio)
9 | Env map[string]string // Environment variables (stdio) or headers (http/sse)
10 | URL string // URL for HTTP/SSE connection
11 | IsSSE bool // Type identifier for URL-based servers (true for SSE, false for HTTP)
12 | }
13 |
14 | // ServerConfigEditor defines the interface for managing server configurations
15 | // in a backend-agnostic way for the TUI.
16 | type ServerConfigEditor interface {
17 | // ListServers retrieves all configured servers (both enabled and disabled).
18 | // It returns a map where the key is the server name.
19 | ListServers() (map[string]CommonServer, error)
20 |
21 | // ListDisabledServers returns the names of disabled servers.
22 | ListDisabledServers() ([]string, error)
23 |
24 | // EnableMCPServer enables a specific server by name.
25 | EnableMCPServer(name string) error
26 |
27 | // DisableMCPServer disables a specific server by name.
28 | DisableMCPServer(name string) error
29 |
30 | // AddMCPServer adds or updates a server configuration.
31 | AddMCPServer(server CommonServer, overwrite bool) error
32 |
33 | // RemoveMCPServer removes a specific server by name.
34 | RemoveMCPServer(name string) error
35 |
36 | // Save persists the configuration changes to the underlying storage.
37 | Save() error
38 |
39 | // GetConfigPath returns the path of the configuration file being managed.
40 | GetConfigPath() string
41 |
42 | // IsServerDisabled checks if a server with the given name is currently disabled.
43 | IsServerDisabled(name string) (bool, error)
44 |
45 | // GetServer retrieves a specific server configuration by name.
46 | GetServer(name string) (CommonServer, bool, error)
47 | }
48 |
--------------------------------------------------------------------------------
/web/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Provider } from 'react-redux';
3 | import { store } from './store/store';
4 | import SearchForm from './components/SearchForm';
5 | import ResultsList from './components/ResultsList';
6 | import ModelInfo from './components/ModelInfo';
7 | import { useRerankMutation } from './services/api';
8 | import type { ArxivPaper, ScoredPaper } from './services/api';
9 |
10 | import 'bootstrap/dist/css/bootstrap.min.css';
11 | import './App.css';
12 |
13 | const AppContent: React.FC = () => {
14 | const [rerank, { isLoading }] = useRerankMutation();
15 | const [results, setResults] = useState([]);
16 | const [searchQuery, setSearchQuery] = useState('');
17 |
18 | const handleSearch = async (query: string, papers: ArxivPaper[]) => {
19 | try {
20 | const response = await rerank({
21 | query,
22 | results: papers,
23 | top_n: 10
24 | }).unwrap();
25 |
26 | setResults(response.reranked_results);
27 | setSearchQuery(query);
28 | } catch (error) {
29 | console.error('Failed to rerank papers:', error);
30 | alert('Failed to rerank papers. Please try again.');
31 | }
32 | };
33 |
34 | return (
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | const App: React.FC = () => {
55 | return (
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default App;
--------------------------------------------------------------------------------
/examples/pages/dino-quiz.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: quiz-title
4 | content: "🎯 Test Your Dinosaur Knowledge!"
5 |
6 | - text:
7 | id: quiz-intro
8 | content: "Think you know your dinosaurs? Take this quiz to prove your expertise!"
9 |
10 | - form:
11 | id: dino-quiz
12 | components:
13 | - title:
14 | content: "Question 1"
15 | - text:
16 | content: "Which dinosaur was the largest carnivore of all time?"
17 | - list:
18 | type: ul
19 | title: "Question 1 Options"
20 | items:
21 | - checkbox:
22 | id: q1-trex
23 | label: "Tyrannosaurus Rex"
24 | - checkbox:
25 | id: q1-spino
26 | label: "Spinosaurus"
27 | - checkbox:
28 | id: q1-giga
29 | label: "Giganotosaurus"
30 |
31 | - title:
32 | content: "Question 2"
33 | - text:
34 | content: "What period did the Stegosaurus live in?"
35 | - list:
36 | type: ul
37 | title: "Question 2 Options"
38 | items:
39 | - checkbox:
40 | id: q2-triassic
41 | label: "Triassic"
42 | - checkbox:
43 | id: q2-jurassic
44 | label: "Jurassic"
45 | - checkbox:
46 | id: q2-cretaceous
47 | label: "Cretaceous"
48 |
49 | - title:
50 | content: "Question 3"
51 | - text:
52 | content: "How many horns did Triceratops have?"
53 | - input:
54 | id: q3-horns
55 | type: number
56 | placeholder: "Enter number"
57 | required: true
58 |
59 | - button:
60 | id: submit-quiz
61 | text: "Submit Answers"
62 | type: primary
63 |
64 | - button:
65 | id: reset-quiz
66 | text: "Start Over"
67 | type: secondary
--------------------------------------------------------------------------------
/examples/pages/cow/cow-quiz.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: quiz-title
4 | content: "🎯 Test Your Cow Knowledge!"
5 |
6 | - text:
7 | id: quiz-intro
8 | content: "Think you know your cows? Take this quiz to prove your expertise!"
9 |
10 | - form:
11 | id: cow-quiz
12 | components:
13 | - title:
14 | content: "Question 1"
15 | - text:
16 | content: "How many stomachs does a cow have?"
17 | - list:
18 | type: ul
19 | title: "Question 1 Options"
20 | items:
21 | - checkbox:
22 | id: q1-two
23 | label: "Two"
24 | - checkbox:
25 | id: q1-three
26 | label: "Three"
27 | - checkbox:
28 | id: q1-four
29 | label: "Four"
30 |
31 | - title:
32 | content: "Question 2"
33 | - text:
34 | content: "Which breed is known for producing the most milk?"
35 | - list:
36 | type: ul
37 | title: "Question 2 Options"
38 | items:
39 | - checkbox:
40 | id: q2-holstein
41 | label: "Holstein-Friesian"
42 | - checkbox:
43 | id: q2-jersey
44 | label: "Jersey"
45 | - checkbox:
46 | id: q2-guernsey
47 | label: "Guernsey"
48 |
49 | - title:
50 | content: "Question 3"
51 | - text:
52 | content: "How many liters of water does a dairy cow drink per day?"
53 | - input:
54 | id: q3-water
55 | type: number
56 | placeholder: "Enter number of liters"
57 | required: true
58 |
59 | - button:
60 | id: submit-quiz
61 | text: "Submit Answers"
62 | type: primary
63 |
64 | - button:
65 | id: reset-quiz
66 | text: "Start Over"
67 | type: secondary
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/cmd/apps/scholarly/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | clay "github.com/go-go-golems/clay/pkg"
7 | "github.com/go-go-golems/glazed/pkg/cmds/logging"
8 | "github.com/go-go-golems/glazed/pkg/help"
9 | helpCmd "github.com/go-go-golems/glazed/pkg/help/cmd"
10 | "github.com/rs/zerolog/log"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | var debugMode bool
15 |
16 | // rootCmd represents the base command when called without any subcommands
17 | var rootCmd = &cobra.Command{
18 | Use: "scholarly",
19 | Short: "A CLI tool to search Arxiv, LibGen, Crossref, and OpenAlex for scientific papers.",
20 | Long: `scholarly is a command-line tool that allows users to search for scientific papers across multiple academic databases and repositories including Arxiv, Library Genesis, Crossref, and OpenAlex.
21 |
22 | It provides specific subcommands for each platform, allowing targeted searches with various filters and options.
23 |
24 | Examples:
25 | scholarly arxiv -q "all:electron" -n 5
26 | scholarly libgen -q "artificial intelligence" -m "https://libgen.is"
27 | scholarly crossref -q "climate change mitigation"
28 | scholarly openalex -q "machine learning applications"`,
29 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
30 | err := logging.InitLoggerFromViper()
31 | if err != nil {
32 | return err
33 | }
34 | return nil
35 | },
36 | }
37 |
38 | // Execute adds all child commands to the root command and sets flags appropriately.
39 | // This is called by main.main(). It only needs to happen once to the rootCmd.
40 | func Execute() {
41 | helpSystem := help.NewHelpSystem()
42 | helpCmd.SetupCobraRootCommand(helpSystem, rootCmd)
43 |
44 | err := clay.InitViper("mcp", rootCmd)
45 | if err != nil {
46 | log.Fatal().Err(err).Msg("Failed to initialize viper")
47 | }
48 |
49 | err = rootCmd.Execute()
50 | if err != nil {
51 | os.Exit(1)
52 | }
53 | }
54 |
55 | func init() {
56 | rootCmd.PersistentFlags().BoolVar(&debugMode, "debug", false, "Enable debug logging")
57 | // Remove the default toggle flag if it exists from the initial cobra init
58 | // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
59 | }
60 |
--------------------------------------------------------------------------------
/doc/scholarly/implementation-summary.md:
--------------------------------------------------------------------------------
1 | # Scholarly Implementation Summary
2 |
3 | ## What we've built
4 |
5 | 1. **API Server Command**: Added a `serve` command to the Scholarly CLI that starts an HTTP server providing RESTful API endpoints for the scholarly search functionality.
6 |
7 | 2. **REST API Endpoints**:
8 | - `/api/search` - Search for papers across multiple sources
9 | - `/api/sources` - List available sources
10 | - `/api/health` - API health check
11 |
12 | 3. **Modern React Frontend**:
13 | - User-friendly interface for searching academic papers
14 | - Advanced filtering and sorting options
15 | - Clean display of paper results with links to sources
16 | - Built with React, TypeScript, TanStack Query, and Bootstrap
17 |
18 | 4. **Integration between API and UI**:
19 | - Frontend communicates with the API via Axios
20 | - API server configured to serve the frontend static files
21 | - Developer workflow with combined start script
22 |
23 | ## Architecture
24 |
25 | The system follows a modern architecture with clean separation of concerns:
26 |
27 | - **Backend**: Go-based API server using the Cobra command framework
28 | - **Frontend**: React SPA with TypeScript and component-based architecture
29 | - **API Communication**: RESTful API with JSON responses
30 |
31 | ## Key Features
32 |
33 | - **Cross-Source Search**: Unified search across ArXiv, Crossref, and OpenAlex
34 | - **Advanced Filtering**: Author, title, year range, category, and more
35 | - **Result Reranking**: Uses a neural reranker to improve result relevance
36 | - **CORS Support**: Configurable CORS settings for API access
37 | - **Static File Serving**: API server serves the built frontend files
38 |
39 | ## Development & Production Workflow
40 |
41 | - **Development**: Combined start script for running both API and UI
42 | - **Production**: Build frontend and serve through the API server
43 | - **Documentation**: API documentation and README files
44 |
45 | ## Future Improvements
46 |
47 | - User authentication and saved searches
48 | - Citation export functionality
49 | - Improved mobile responsive design
50 | - Result caching for improved performance
51 | - Integration with additional academic sources
--------------------------------------------------------------------------------
/pkg/scholarly/clients/arxiv/models.go:
--------------------------------------------------------------------------------
1 | package arxiv
2 |
3 | import "encoding/xml"
4 |
5 | // AtomFeed represents the top-level structure of the Arxiv API response.
6 | // Based on Atom 1.0 and Arxiv API specifics.
7 | type AtomFeed struct {
8 | XMLName xml.Name `xml:"feed"`
9 | Title string `xml:"title"`
10 | ID string `xml:"id"`
11 | Updated string `xml:"updated"`
12 | Entries []Entry `xml:"entry"`
13 | TotalResults int `xml:"totalResults"` // OpenSearch extension
14 | StartIndex int `xml:"startIndex"` // OpenSearch extension
15 | ItemsPerPage int `xml:"itemsPerPage"` // OpenSearch extension
16 | }
17 |
18 | // Entry represents a single paper in the Arxiv API response.
19 | // Based on Atom 1.0 and Arxiv API specifics.
20 | type Entry struct {
21 | ID string `xml:"id"` // Usually the Arxiv URL for the paper
22 | Updated string `xml:"updated"`
23 | Published string `xml:"published"`
24 | Title string `xml:"title"`
25 | Summary string `xml:"summary"` // Abstract
26 | Authors []Author `xml:"author"`
27 | DOI string `xml:"doi"` // Arxiv extension
28 | Comment string `xml:"comment"` // Arxiv extension
29 | JournalRef string `xml:"journal_ref"` // Arxiv extension
30 | Link []Link `xml:"link"`
31 | PrimaryCategory Category `xml:"primary_category"` // Arxiv extension
32 | Categories []Category `xml:"category"`
33 | }
34 |
35 | // Author represents an author of a paper.
36 | // Based on Atom 1.0.
37 | type Author struct {
38 | Name string `xml:"name"`
39 | }
40 |
41 | // Link represents a link related to the paper (e.g., PDF, abstract page).
42 | // Based on Atom 1.0.
43 | type Link struct {
44 | Href string `xml:"href,attr"`
45 | Rel string `xml:"rel,attr,omitempty"`
46 | Type string `xml:"type,attr,omitempty"`
47 | Title string `xml:"title,attr,omitempty"`
48 | }
49 |
50 | // Category represents a subject category of the paper.
51 | // Based on Atom 1.0 and Arxiv extension.
52 | type Category struct {
53 | Term string `xml:"term,attr"`
54 | Scheme string `xml:"scheme,attr,omitempty"`
55 | }
56 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-scanning.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/dependency-scanning.yml
2 | name: Dependency Scanning
3 |
4 | on:
5 | push:
6 | branches: [ main ]
7 | pull_request:
8 | branches: [ main ]
9 | schedule:
10 | - cron: '0 0 * * 0' # Run weekly on Sunday at midnight
11 |
12 | jobs:
13 | dependency-review:
14 | name: Dependency Review
15 | runs-on: ubuntu-latest
16 | if: github.event_name == 'pull_request'
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v5
20 |
21 | - name: Dependency Review
22 | uses: actions/dependency-review-action@v4
23 | with:
24 | fail-on-severity: high
25 |
26 | govulncheck:
27 | name: Go Vulnerability Check
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v5
32 |
33 | - name: Set up Go
34 | uses: actions/setup-go@v5
35 | with:
36 | go-version: '1.24'
37 |
38 | - name: Install govulncheck
39 | run: go install golang.org/x/vuln/cmd/govulncheck@latest
40 |
41 | - name: Run govulncheck
42 | run: govulncheck ./...
43 |
44 | nancy:
45 | name: Nancy Vulnerability Scan
46 | runs-on: ubuntu-latest
47 | steps:
48 | - name: Checkout code
49 | uses: actions/checkout@v5
50 |
51 | - name: Set up Go
52 | uses: actions/setup-go@v5
53 | with:
54 | go-version: '1.24'
55 |
56 | - name: Install Nancy
57 | run: go install github.com/sonatype-nexus-community/nancy@latest
58 |
59 | - name: Run Nancy
60 | run: go list -json -deps ./... | nancy sleuth
61 |
62 | gosec:
63 | name: GoSec Security Scan
64 | runs-on: ubuntu-latest
65 | steps:
66 | - name: Checkout code
67 | uses: actions/checkout@v5
68 |
69 | - name: Set up Go
70 | uses: actions/setup-go@v5
71 | with:
72 | go-version: '1.24'
73 |
74 | - name: Run Gosec Security Scanner
75 | uses: securego/gosec@master
76 | with:
77 | args: -exclude=G101,G304,G301,G306,G204 -exclude-dir=.history ./...
--------------------------------------------------------------------------------
/ttmp/2025-02-21/ui-dsl.yaml:
--------------------------------------------------------------------------------
1 | # UI DSL Schema
2 | # Each component has common attributes:
3 | # - id: unique identifier (optional)
4 | # - style: inline CSS (optional)
5 | # - disabled: boolean (optional)
6 | # - data: map of data attributes (optional)
7 |
8 | # Example components:
9 | ---
10 | components:
11 | - button:
12 | text: Click me
13 | type: primary # primary, secondary, danger, success
14 | id: submit-btn
15 | disabled: false
16 | onclick: alert('clicked')
17 |
18 | - title:
19 | content: Welcome to My App
20 | id: main-title
21 |
22 | - text:
23 | content: This is a paragraph of text that explains something.
24 | id: description
25 |
26 | - input:
27 | type: text # text, email, password, number, tel
28 | placeholder: Enter your name
29 | value: ""
30 | required: true
31 | id: name-input
32 | data:
33 | validate: true
34 | maxlength: 50
35 |
36 | - textarea:
37 | placeholder: Enter description
38 | rows: 4
39 | cols: 50
40 | value: |
41 | Default multiline
42 | text content
43 |
44 | - checkbox:
45 | label: Accept terms
46 | checked: false
47 | required: true
48 | name: terms
49 | id: terms-checkbox
50 |
51 | - list:
52 | type: ul # ul or ol
53 | title: Example List
54 | items:
55 | - text:
56 | content: First item
57 | - text:
58 | content: Second item
59 | - text:
60 | content: Third item
61 | - button:
62 | text: Click me
63 | type: secondary
64 |
65 | - form:
66 | id: signup-form
67 | components:
68 | - title:
69 | content: Sign Up
70 | - text:
71 | content: Please fill in your details below.
72 | - input:
73 | type: email
74 | placeholder: Email address
75 | required: true
76 | - input:
77 | type: password
78 | placeholder: Password
79 | required: true
80 | - checkbox:
81 | label: Subscribe to newsletter
82 | - button:
83 | text: Submit
84 | type: primary
--------------------------------------------------------------------------------
/examples/pages/cow/build-a-cow.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: build-cow-title
4 | content: "🐮 Design Your Dream Cow"
5 |
6 | - text:
7 | id: build-intro
8 | content: "Create your perfect bovine companion! Mix and match features to design your unique cow."
9 |
10 | - form:
11 | id: cow-builder
12 | components:
13 | - input:
14 | id: cow-name
15 | type: text
16 | placeholder: "Name your cow"
17 | required: true
18 |
19 | - list:
20 | type: ul
21 | items:
22 | - input:
23 | id: cow-weight
24 | type: number
25 | placeholder: "Weight (kg)"
26 | - input:
27 | id: cow-height
28 | type: number
29 | placeholder: "Height (cm)"
30 |
31 | - textarea:
32 | id: cow-description
33 | placeholder: "Describe your cow's personality..."
34 | rows: 4
35 | cols: 50
36 |
37 | - text:
38 | content: "Select coat pattern:"
39 |
40 | - list:
41 | type: ul
42 | items:
43 | - checkbox:
44 | id: pattern-holstein
45 | label: "Holstein (black and white)"
46 | - checkbox:
47 | id: pattern-jersey
48 | label: "Jersey (solid brown)"
49 | - checkbox:
50 | id: pattern-hereford
51 | label: "Hereford (red and white)"
52 |
53 | - text:
54 | content: "Special traits:"
55 |
56 | - list:
57 | type: ul
58 | items:
59 | - checkbox:
60 | id: trait-friendly
61 | label: "Extra Friendly"
62 | - checkbox:
63 | id: trait-milk
64 | label: "High Milk Production"
65 | - checkbox:
66 | id: trait-grass
67 | label: "Grass Connoisseur"
68 |
69 | - button:
70 | id: create-cow-btn
71 | text: "Create Cow"
72 | type: primary
--------------------------------------------------------------------------------
/examples/shell-commands/fetch-url.yaml:
--------------------------------------------------------------------------------
1 | name: fetch-url
2 | short: Fetch and extract text content from URLs using lynx
3 | long: |
4 | This command fetches and extracts plain text content from one or more URLs using the lynx text browser.
5 | It's particularly useful for automated content extraction, web scraping, and integration with LLM tools.
6 |
7 | INPUT: One or more URLs to fetch, with optional configuration for the output format and processing
8 | OUTPUT: Plain text content from the specified URLs, with configurable formatting
9 |
10 | The command uses lynx's dump mode to convert HTML content to plain text while:
11 | - Removing HTML formatting
12 | - Preserving text structure and hierarchy
13 | - Maintaining readable link references
14 | - Handling various character encodings
15 |
16 | Common use cases:
17 | 1. Extract article content for summarization: --urls https://example.com/article
18 | 2. Batch process multiple URLs: --urls https://site1.com,https://site2.com
19 | 3. Custom formatting: --urls https://example.com --no-links
20 |
21 | Example outputs:
22 | ```
23 | Article Title
24 | ============
25 |
26 | Main content text here...
27 |
28 | References
29 | [1] http://reference1.com
30 | [2] http://reference2.com
31 | ```
32 |
33 | flags:
34 | - name: urls
35 | type: stringList
36 | help: |
37 | URLs to fetch content from. Multiple URLs can be specified as comma-separated values.
38 | Each URL should be a valid HTTP/HTTPS URL.
39 | Example: --urls https://example.com,https://another.com
40 | required: true
41 |
42 | - name: no_links
43 | type: bool
44 | help: |
45 | If true, removes the reference links section from the output.
46 | Useful when you only want the main content without link references.
47 | Example: --no-links
48 | default: false
49 |
50 | shell-script: |
51 | #!/bin/bash
52 | set -euo pipefail
53 |
54 | # Process each URL
55 | for url in {{ range .Args.urls }}"{{ . }}" {{ end }}; do
56 | echo "Fetching $url..."
57 |
58 | # Build lynx command
59 | lynx_cmd="lynx -dump "
60 | {{ if .Args.no_links }}
61 | lynx_cmd+="--nolist "
62 | {{ end }}
63 |
64 | # Execute fetch
65 | eval "$lynx_cmd '$url'"
66 | echo -e "\n---\n"
67 | done
--------------------------------------------------------------------------------
/pkg/helpers/file_helpers.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "io"
5 | "os"
6 | )
7 |
8 | // FindStartPosForLastNLines finds the position in a file where the last N lines begin.
9 | // It returns the byte offset from the start of the file and any error encountered.
10 | // If n <= 0, it returns 0 (start of file).
11 | // If the file has fewer lines than n, it returns 0 (start of file).
12 | func FindStartPosForLastNLines(filename string, n int) (int64, error) {
13 | if n <= 0 {
14 | return 0, nil
15 | }
16 |
17 | file, err := os.Open(filename)
18 | if err != nil {
19 | return 0, err
20 | }
21 | defer func() {
22 | if closeErr := file.Close(); closeErr != nil {
23 | if err == nil {
24 | err = closeErr
25 | }
26 | }
27 | }()
28 |
29 | // Get file size
30 | stat, err := file.Stat()
31 | if err != nil {
32 | return 0, err
33 | }
34 | fileSize := stat.Size()
35 | if fileSize == 0 {
36 | return 0, nil
37 | }
38 |
39 | // Start from end
40 | pos := fileSize
41 | // Use a 4KB buffer size
42 | buf := make([]byte, 4096)
43 | linesFound := 0
44 |
45 | // Handle the case where the file doesn't end with a newline
46 | // by treating the end of file as an implicit line ending
47 | lastByteRead := false
48 | lastByteWasNewline := false
49 |
50 | for pos > 0 && linesFound <= n {
51 | // Calculate how much to read
52 | bytesToRead := int64(len(buf))
53 | if pos < bytesToRead {
54 | bytesToRead = pos
55 | }
56 |
57 | // Read a chunk from the right position
58 | pos -= bytesToRead
59 | _, err := file.Seek(pos, io.SeekStart)
60 | if err != nil {
61 | return 0, err
62 | }
63 |
64 | // Read the chunk
65 | bytesRead, err := file.Read(buf[:bytesToRead])
66 | if err != nil {
67 | return 0, err
68 | }
69 |
70 | // Count newlines in this chunk, going backwards
71 | for i := bytesRead - 1; i >= 0; i-- {
72 | if !lastByteRead {
73 | lastByteRead = true
74 | lastByteWasNewline = buf[i] == '\n'
75 | continue
76 | }
77 |
78 | _ = lastByteWasNewline
79 |
80 | if buf[i] == '\n' {
81 | linesFound++
82 | if linesFound >= n {
83 | // Add 1 to skip the newline itself
84 | return pos + int64(i) + 1, nil
85 | }
86 | }
87 | }
88 | }
89 |
90 | // If we get here, we need to read from the start of the file
91 | return 0, nil
92 | }
93 |
--------------------------------------------------------------------------------
/examples/bio-stuff/fetch-url.yaml:
--------------------------------------------------------------------------------
1 | name: fetch-url
2 | short: Fetch and extract text content from URLs using lynx. foo. foo. Foo.
3 | long: |
4 | This command fetches and extracts plain text content from one or more URLs using the lynx text browser.
5 | It's particularly useful for automated content extraction, web scraping, and integration with LLM tools.
6 |
7 | INPUT: One or more URLs to fetch, with optional configuration for the output format and processing
8 | OUTPUT: Plain text content from the specified URLs, with configurable formatting
9 |
10 | The command uses lynx's dump mode to convert HTML content to plain text while:
11 | - Removing HTML formatting
12 | - Preserving text structure and hierarchy
13 | - Maintaining readable link references
14 | - Handling various character encodings
15 |
16 | Common use cases:
17 | 1. Extract article content for summarization: --urls https://example.com/article
18 | 2. Batch process multiple URLs: --urls https://site1.com,https://site2.com
19 | 3. Custom formatting: --urls https://example.com --no-links
20 |
21 | Example outputs:
22 | ```
23 | Article Title
24 | ============
25 |
26 | Main content text here...
27 |
28 | References
29 | [1] http://reference1.com
30 | [2] http://reference2.com
31 | ```
32 |
33 | flags:
34 | - name: urls
35 | type: stringList
36 | help: |
37 | URLs to fetch content from. Multiple URLs can be specified as comma-separated values.
38 | Each URL should be a valid HTTP/HTTPS URL.
39 | Example: --urls https://example.com,https://another.com
40 | required: true
41 |
42 | - name: no_links
43 | type: bool
44 | help: |
45 | If true, removes the reference links section from the output.
46 | Useful when you only want the main content without link references.
47 | Example: --no-links
48 | default: false
49 |
50 | shell-script: |
51 | #!/bin/bash
52 | set -euo pipefail
53 |
54 | # Process each URL
55 | for url in {{ range .Args.urls }}"{{ . }}" {{ end }}; do
56 | echo "Fetching $url..."
57 |
58 | # Build lynx command
59 | lynx_cmd="lynx -dump "
60 | {{ if .Args.no_links }}
61 | lynx_cmd+="--nolist "
62 | {{ end }}
63 |
64 | # Execute fetch
65 | eval "$lynx_cmd '$url'"
66 | echo -e "\n---\n"
67 | done
--------------------------------------------------------------------------------
/examples/sqleton/sql-query.yaml:
--------------------------------------------------------------------------------
1 | name: sql-query
2 | short: Execute SQL query using sqleton
3 | long: |
4 | Execute a SQL query against a database using sqleton.
5 | The query is properly escaped to ensure safe execution.
6 | Results are displayed in CSV format by default.
7 |
8 | Provide one or more queries with the queries parameter.
9 | Multi-line SQL queries are supported.
10 |
11 | flags:
12 | - name: queries
13 | type: stringList
14 | help: List of SQL queries to execute
15 | required: true
16 | - name: format
17 | type: choice
18 | help: Output format
19 | choices:
20 | - table
21 | - json
22 | - csv
23 | default: csv
24 | - name: dbt_profile
25 | type: string
26 | help: DBT profile to use for connection
27 | required: false
28 |
29 | shell-script: |
30 | #!/bin/bash
31 | set -euo pipefail
32 |
33 | # Get args from JSON file
34 | ARGS_FILE="$MCP_ARGUMENTS_JSON_PATH"
35 | echo "ARGS_FILE: $ARGS_FILE"
36 |
37 | # Extract format and optional dbt profile
38 | FORMAT=$(jq -r '.format' "$ARGS_FILE")
39 |
40 | # Check if dbt_profile is specified
41 | DBT_PROFILE_ARGS=""
42 | if jq -e '.dbt_profile' "$ARGS_FILE" > /dev/null 2>&1; then
43 | DBT_PROFILE=$(jq -r '.dbt_profile' "$ARGS_FILE")
44 | DBT_PROFILE_ARGS="--dbt-profile $DBT_PROFILE"
45 | echo "Using DBT profile: $DBT_PROFILE"
46 | fi
47 |
48 | # Create a temporary file for each query
49 | TMP_DIR=$(mktemp -d)
50 | trap 'rm -rf "$TMP_DIR"' EXIT
51 |
52 | # Extract each query to a separate file to preserve multiline formatting
53 | QUERY_COUNT=$(jq '.queries | length' "$ARGS_FILE")
54 | for ((i=0; i "$QUERY_FILE"
57 |
58 | echo "Executing query #$((i+1)):"
59 | # Print the query with line numbers for debugging
60 | cat -n "$QUERY_FILE"
61 |
62 | # Execute sqleton with the query file
63 | # shellcheck disable=SC2086
64 | sqleton query "$(cat "$QUERY_FILE")" --output "$FORMAT" $DBT_PROFILE_ARGS
65 |
66 | # Add a separator between query results
67 | if [ "$FORMAT" = "csv" ]; then
68 | echo "----------------------------------------"
69 | fi
70 | done
71 |
72 | # Save debug information
73 | save-script-dir: /tmp/sqleton-scripts
74 | debug: true
75 | capture-stderr: true
--------------------------------------------------------------------------------
/examples/pages/cow/dairy-farm-guide.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: farm-guide-title
4 | content: "🚜 Welcome to Happy Meadows Dairy Farm"
5 |
6 |
7 | - text:
8 | id: farm-intro
9 | content: "Experience the joy of farm life! Learn about our cows and see how a modern dairy farm operates. Please read these guidelines before your visit."
10 |
11 | - list:
12 | type: ul
13 | title: "Farm Areas"
14 |
15 | items:
16 | - text:
17 |
18 | content: "Milking Parlor"
19 | - button:
20 | id: milking-map
21 | text: "View Area"
22 | type: secondary
23 | - text:
24 | content: "Grazing Pastures"
25 | - button:
26 | id: pasture-map
27 | text: "View Area"
28 | type: secondary
29 | - text:
30 | content: "Calf Barn"
31 | - button:
32 | id: calf-map
33 | text: "View Area"
34 | type: secondary
35 |
36 |
37 | - title:
38 | content: "Visitor Guidelines"
39 |
40 | - list:
41 | type: ol
42 | title: "Safety Rules"
43 | items:
44 | - text:
45 | content: "Stay with your guide at all times"
46 | - text:
47 | content: "Keep quiet around the animals"
48 | - text:
49 | content: "Follow farm safety rules"
50 | - button:
51 | id: safety-info
52 | text: "Safety Guide"
53 | type: primary
54 | - text:
55 | content: "Don't feed the animals without permission"
56 |
57 | - form:
58 | id: tour-booking
59 | components:
60 | - title:
61 | content: "Book a Farm Tour"
62 | - input:
63 | id: visitor-name
64 | type: text
65 | placeholder: "Your name"
66 | required: true
67 | - input:
68 | id: group-size
69 | type: number
70 | placeholder: "Number of visitors"
71 | required: true
72 | - checkbox:
73 | id: premium-tour
74 | label: "Premium Tour (includes cheese tasting)"
75 | - button:
76 | id: book-tour
77 | text: "Book Now"
78 | type: success
79 |
80 | - text:
81 | id: contact-info
82 | content: "For farm emergencies, call our staff at 555-FARM"
--------------------------------------------------------------------------------
/examples/pages/dino-park-guide.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | id: park-guide-title
4 | content: "🦕 Welcome to Prehistoric Park"
5 |
6 | - text:
7 | id: park-intro
8 | content: "Your guide to the most amazing dinosaur experience on Earth! Please read these guidelines carefully before your visit."
9 |
10 | - list:
11 | type: ul
12 | title: "Park Areas"
13 | items:
14 | - text:
15 | content: "Herbivore Valley"
16 | - button:
17 | id: herbivore-map
18 | text: "View Map"
19 | type: secondary
20 | - text:
21 | content: "Carnivore Territory"
22 | - button:
23 | id: carnivore-map
24 | text: "View Map"
25 | type: secondary
26 | - text:
27 | content: "Prehistoric Garden"
28 | - button:
29 | id: garden-map
30 | text: "View Map"
31 | type: secondary
32 |
33 | - title:
34 | content: "Safety Guidelines"
35 |
36 | - list:
37 | type: ol
38 | title: "Important Safety Rules"
39 | items:
40 | - text:
41 | content: "Stay in your vehicle at all times in designated areas"
42 | - text:
43 | content: "Keep windows closed in carnivore territories"
44 | - text:
45 | content: "Follow guide instructions"
46 | - button:
47 | id: guide-info
48 | text: "Meet Our Guides"
49 | type: primary
50 | - text:
51 | content: "Maintain safe distance from enclosures"
52 |
53 | - form:
54 | id: tour-booking
55 | components:
56 | - title:
57 | content: "Book a Guided Tour"
58 | - input:
59 | id: visitor-name
60 | type: text
61 | placeholder: "Your name"
62 | required: true
63 | - input:
64 | id: group-size
65 | type: number
66 | placeholder: "Number of visitors"
67 | required: true
68 | - checkbox:
69 | id: vip-tour
70 | label: "VIP Tour (includes feeding experience)"
71 | - button:
72 | id: book-tour
73 | text: "Book Now"
74 | type: success
75 |
76 | - text:
77 | id: emergency-info
78 | content: "In case of emergency, call park security at 555-DINO"
--------------------------------------------------------------------------------
/pkg/scholarly/tools/get_metrics.go:
--------------------------------------------------------------------------------
1 | package tools
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/go-go-golems/go-go-mcp/pkg/scholarly/clients/openalex"
8 | "github.com/go-go-golems/go-go-mcp/pkg/scholarly/common"
9 | "github.com/rs/zerolog/log"
10 | )
11 |
12 | // GetMetrics retrieves quantitative metrics for a work
13 | func GetMetrics(req common.GetMetricsRequest) (*common.Metrics, error) {
14 | if req.WorkID == "" {
15 | return nil, fmt.Errorf("work_id cannot be empty")
16 | }
17 |
18 | log.Debug().Str("work_id", req.WorkID).Msg("Getting metrics")
19 |
20 | // Check if the ID is a DOI
21 | if isValidDOI(req.WorkID) {
22 | return getMetricsByDOI(req.WorkID)
23 | }
24 |
25 | // Otherwise, assume it's an OpenAlex ID
26 | return getMetricsByOpenAlexID(req.WorkID)
27 | }
28 |
29 | // getMetricsByDOI gets metrics for a work identified by DOI
30 | func getMetricsByDOI(doi string) (*common.Metrics, error) {
31 | // Resolve the DOI to get the OpenAlex ID
32 | resolveReq := common.ResolveDOIRequest{
33 | DOI: doi,
34 | }
35 |
36 | work, err := ResolveDOI(resolveReq)
37 | if err != nil {
38 | return nil, fmt.Errorf("error resolving DOI: %w", err)
39 | }
40 |
41 | // Now get metrics by OpenAlex ID
42 | return getMetricsByOpenAlexID(work.ID)
43 | }
44 |
45 | // getMetricsByOpenAlexID gets metrics for a work identified by OpenAlex ID
46 | func getMetricsByOpenAlexID(workID string) (*common.Metrics, error) {
47 | client := openalex.NewClient("")
48 |
49 | // Use direct works endpoint
50 | work, err := client.GetWorkByDOI(workID)
51 | if err != nil {
52 | // If we have Crossref data, we can still return some metrics
53 | if strings.Contains(err.Error(), "not found") {
54 | // Return metrics with just the data we have
55 | return &common.Metrics{
56 | CitationCount: 0,
57 | CitedByCount: 0,
58 | ReferenceCount: 0,
59 | IsOA: false,
60 | Altmetrics: make(map[string]int),
61 | }, nil
62 | }
63 | return nil, fmt.Errorf("OpenAlex error: %w", err)
64 | }
65 |
66 | // Extract metrics from the result
67 | metrics := &common.Metrics{
68 | CitationCount: work.CitationCount,
69 | CitedByCount: work.CitationCount, // Same as citation_count in OpenAlex
70 | ReferenceCount: 0, // Not directly available
71 | IsOA: work.IsOA,
72 | Altmetrics: make(map[string]int),
73 | }
74 |
75 | return metrics, nil
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/session/store.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/google/uuid"
7 | )
8 |
9 | // SessionStore defines the interface for managing sessions.
10 | type SessionStore interface {
11 | Get(sessionID SessionID) (*Session, bool)
12 | Create() *Session
13 | Update(session *Session) // Note: For in-memory, Get returns a pointer, so Update might be implicit.
14 | Delete(sessionID SessionID)
15 | // TODO: Add cleanup mechanism for stale sessions
16 | }
17 |
18 | // InMemorySessionStore provides a thread-safe in-memory implementation of SessionStore.
19 | type InMemorySessionStore struct {
20 | mu sync.RWMutex
21 | sessions map[SessionID]*Session
22 | }
23 |
24 | // NewInMemorySessionStore creates a new in-memory session store.
25 | func NewInMemorySessionStore() *InMemorySessionStore {
26 | return &InMemorySessionStore{
27 | sessions: make(map[SessionID]*Session),
28 | }
29 | }
30 |
31 | // Get retrieves a session by its ID.
32 | func (s *InMemorySessionStore) Get(sessionID SessionID) (*Session, bool) {
33 | s.mu.RLock()
34 | defer s.mu.RUnlock()
35 | session, ok := s.sessions[sessionID]
36 | return session, ok
37 | }
38 |
39 | // Create generates a new session with a unique ID and adds it to the store.
40 | func (s *InMemorySessionStore) Create() *Session {
41 | s.mu.Lock()
42 | defer s.mu.Unlock()
43 |
44 | newSession := &Session{
45 | ID: SessionID(uuid.NewString()),
46 | State: make(SessionState),
47 | }
48 | s.sessions[newSession.ID] = newSession
49 | return newSession
50 | }
51 |
52 | // Update stores the potentially modified session state.
53 | // For this in-memory store using pointers, changes to the retrieved session
54 | // are inherently reflected. This method ensures the session exists.
55 | func (s *InMemorySessionStore) Update(session *Session) {
56 | s.mu.Lock()
57 | defer s.mu.Unlock()
58 | // Just ensure it's in the map, assuming modifications happened on the pointer
59 | if _, exists := s.sessions[session.ID]; exists {
60 | s.sessions[session.ID] = session
61 | }
62 | // Optionally, add logic here if sessions need explicit saving or validation
63 | }
64 |
65 | // Delete removes a session from the store.
66 | func (s *InMemorySessionStore) Delete(sessionID SessionID) {
67 | s.mu.Lock()
68 | defer s.mu.Unlock()
69 | delete(s.sessions, sessionID)
70 | }
71 |
72 | // Ensure InMemorySessionStore implements SessionStore
73 | var _ SessionStore = (*InMemorySessionStore)(nil)
74 |
--------------------------------------------------------------------------------
/examples/pages/gorilla.yaml:
--------------------------------------------------------------------------------
1 | components:
2 | - title:
3 | content: 🦍 GORILLA GAME 🦍
4 |
5 | - text:
6 | content: Lead your gorilla through the jungle, collect bananas, and avoid dangers!
7 |
8 | - form:
9 | id: game-controls
10 | components:
11 | - list:
12 | type: ul
13 | title: "Game Status"
14 | items:
15 | - text:
16 | content: "❤️❤️❤️"
17 | id: health-display
18 | - text:
19 | content: "🍌 0"
20 | id: score-display
21 | - text:
22 | content: "🌴 1"
23 | id: level-display
24 |
25 | - list:
26 | type: ul
27 | title: "Movement Controls"
28 | items:
29 | - button:
30 | text: "⬅️ Left"
31 | type: primary
32 | id: move-left
33 | - button:
34 | text: "⬆️ Jump"
35 | type: primary
36 | id: move-up
37 | - button:
38 | text: "➡️ Right"
39 | type: primary
40 | id: move-right
41 |
42 | - list:
43 | type: ul
44 | title: "Game Controls"
45 | items:
46 | - button:
47 | text: "🎮 Start Game"
48 | type: success
49 | id: start-game
50 | - button:
51 | text: "⏸️ Pause"
52 | type: secondary
53 | id: pause-game
54 | - button:
55 | text: "🔄 Restart"
56 | type: danger
57 | id: restart-game
58 |
59 | - form:
60 | id: game-settings
61 | components:
62 | - title:
63 | content: Settings
64 | - checkbox:
65 | label: "🔊 Sound Effects"
66 | checked: true
67 | id: sound-toggle
68 | - checkbox:
69 | label: "🎵 Background Music"
70 | checked: true
71 | id: music-toggle
72 | - input:
73 | type: text
74 | placeholder: Enter player name
75 | id: player-name
76 | required: true
77 |
78 | - text:
79 | content: "High Scores 🏆"
80 |
81 | - list:
82 | type: ol
83 | title: "Top Players"
84 | items:
85 | - text:
86 | content: "King Kong: 2000 🍌"
87 | - text:
88 | content: "Mighty Joe: 1500 🍌"
89 | - text:
90 | content: "Donkey Kong: 1000 🍌"
--------------------------------------------------------------------------------
/ttmp/2025-01-26/04-simplify-html.md:
--------------------------------------------------------------------------------
1 | # HTML Simplification and Minimization Tool Specification
2 |
3 | ## Overview
4 | This tool is designed to simplify and minimize HTML documents by removing unnecessary elements and attributes, and by shortening overly long text content. The output is provided in a structured YAML format for easy readability and further processing.
5 |
6 | ## Core Design Principles
7 | - Efficiently process large HTML files
8 | - Provide clear, structured output in YAML format
9 | - Handle malformed HTML gracefully
10 | - Focus on reducing document size while preserving essential content
11 |
12 | ## Functionality
13 |
14 | ### Input Parameters
15 | ```yaml
16 | file: string # Path to HTML file
17 | options:
18 | strip_scripts: boolean # Remove