├── 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 |
6 | 14 |
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 |
37 |

ArXiv Paper Reranker

38 |

Rerank arXiv paper search results based on query relevance using cross-encoders

39 |
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