diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a2e3ce0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Shell scripts and CI workflows must stay LF — CRLF breaks bash on Linux runners. +*.sh text eol=lf +*.yml text eol=lf +*.yaml text eol=lf diff --git a/.github/workflows/mcp-drift-check.yml b/.github/workflows/mcp-drift-check.yml new file mode 100644 index 0000000..b84aeac --- /dev/null +++ b/.github/workflows/mcp-drift-check.yml @@ -0,0 +1,50 @@ +name: MCP connection drift check + +# Verifies the live AgentBase MCP still matches what the packs ship (endpoint +# host + OAuth auth model). The failure this exists for is the server moving +# under a still repo — caught by the weekly cron, where a red check alone would +# go unseen, so a scheduled/dispatch failure also opens (or comments on) an issue. + +on: + pull_request: + schedule: + - cron: "17 6 * * 1" # Mondays 06:17 UTC + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + drift: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check MCP connection contract + run: bash scripts/check-mcp-drift.sh + + - name: Open an issue on drift + # On a PR the failing check is visible to the author; a cron/dispatch run + # has no PR, so surface it as an issue instead of a silent red check. + if: failure() && github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + title="MCP connection drift detected" + body=$(printf '%s\n' \ + "The scheduled MCP drift check failed." \ + "" \ + "The AgentBase MCP connection contract no longer matches what the plugins ship — the endpoint host moved, OAuth is no longer advertised, or a manifest regressed to a static-token auth model." \ + "" \ + "Failing run: $RUN_URL" \ + "" \ + "Fix: update the canonical URL / auth model in the manifests and skills (and scripts/check-mcp-drift.sh) to match the live server, then re-run this workflow.") + existing=$(gh issue list --state open --search "in:title \"$title\"" --json number --jq '.[0].number // empty') + if [ -n "$existing" ]; then + gh issue comment "$existing" --body "$body" + else + gh issue create --title "$title" --body "$body" --label drift \ + || gh issue create --title "$title" --body "$body" + fi diff --git a/agentbase-browser/skills/byg-et-flow/SKILL.md b/agentbase-browser/skills/byg-et-flow/SKILL.md new file mode 100644 index 0000000..f5abbca --- /dev/null +++ b/agentbase-browser/skills/byg-et-flow/SKILL.md @@ -0,0 +1,51 @@ +--- +name: byg-et-flow +description: Interviewer brugeren og bygger et AgentBase-flow, der automatiserer en proces. Brug når brugeren vil automatisere en opgave eller bygge et flow/en agent — fx "byg et flow der ...", "jeg vil automatisere X", "kan du lave en agent der læser fakturaer", "lav noget der sorterer mine dokumenter". Kræver at AgentBase-værktøjerne er tilsluttet; er de ikke det, så kør kom-i-gang først. +--- + +# Byg et flow + +Du bygger et flow ved at **interviewe brugeren, bekræfte en plan i chatten, bygge +via AgentBase-værktøjerne, køre på en rigtig prøve og gentage.** Brugeren sidder +*ikke* og kigger på lærredet (det er i en anden fane) — så hvert holdepunkt skal +stå synligt i **chatten**, på klart dansk. + +## Før du bygger: intake — alle fem punkter + +Byg ikke, før du har dem. Mangler noget, så **spørg** — gæt aldrig. Et flow bygget +på ét eksempel passer til det ene eksempel, ikke til processen. + +1. **Processen i almindeligt sprog** + de regler, den følger. +2. **De forskellige situationer/varianter**, der findes i praksis (fx "faktura uden + momslinje", "scannet vs. digital PDF", "kontrakt dateret med ord"). +3. **Mindst ét rigtigt eksempel-input** at køre på. +4. **Hvad det færdige output er**, og hvem der læser det. +5. **Hvor det menneskelige godkendelsestrin** (Human-in-the-Loop) skal sidde — fx + før en faktura bogføres, eller et dokument sendes videre. + +## Byggeløkken + +1. **Interview** til alle fem intake-punkter er dækket. +2. **Bekræft planen i chatten.** Beskriv i klart dansk, hvilke byggeklodser flowet + får, og hvad det gør — og få et ja, *før* du bygger. Lærredet er usynligt for + brugeren, så teksten i chatten er deres eneste indblik. +3. **Byg.** Læs mekanikken fra MCP'ens egen vejledning (ressourcen + `guide://flow-building` og prompten `build_flow`), og se `references/flow-eksempler.md` + for en form at starte fra. Bekræft hvert trins præcise håndtag med `get_node_type` + — gæt aldrig navne. Læg **hele grafen i ét `set_flow`**; byg ikke inkrementelt + med `add_node`/`connect_nodes` (de læser-ændrer-skriver og overskriver hinanden). +4. **Kør på en rigtig prøve.** Vedhæft eksemplet, kør flowet, og vis brugeren det + *faktiske* output — ikke det, du gætter på, det gør. +5. **Gentag.** Vend tilbage til brugeren ved hver beslutning eller manglende + oplysning; ret og kør igen, til det er pålideligt. Stop ikke ved "gemt". + +## Holdepunkter skal stå i chatten + +Brugeren ser ikke lærredet. Derfor: bekræft planen i tekst før du bygger; efter en +kørsel — vis eller opsummér det rigtige output; og nævn åbent enhver antagelse, du +var nødt til at gøre. Sig aldrig bare "færdig" — vis resultatet. + +## Sprog + +Alt, brugeren ser (labels, instruktioner, skabelontekst, prompts), skrives på +**dansk**. Node-id'er kan være korte ASCII (fx `input_1`). diff --git a/agentbase-browser/skills/byg-et-flow/references/flow-eksempler.md b/agentbase-browser/skills/byg-et-flow/references/flow-eksempler.md new file mode 100644 index 0000000..64bb208 --- /dev/null +++ b/agentbase-browser/skills/byg-et-flow/references/flow-eksempler.md @@ -0,0 +1,61 @@ +# Flow-former at starte fra + +Genkendelige mønstre for AgentBase-flows. Brug dem til at foreslå brugeren en +form — vælg den, der passer til processen, og byg videre derfra. **Byggeklods- +navnene her er til orientering; bekræft altid de præcise håndtag (handles) med +`get_node_type`, og hent den kopiklare graf fra MCP'ens egne `example://`- +ressourcer.** MCP'ens `guide://flow-building` er den tekniske kanon. + +## 1. Dokument → struktureret data → regneark + +Læs et dokument, træk bestemte felter ud som struktureret data, og skriv dem i +faste kolonner i et regneark. + +- **Byggeklodser:** Dokument-OCR → Sprogmodel (med `response_schema`) → JSON til Excel. + Til mange filer på én gang: Dokumentliste → Fil til struktureret data → JSON til Excel. +- **Brug den til:** fakturaer, kontrakter eller ansøgninger, hvor bestemte felter + skal ud i faste kolonner. +- **MCP-eksempler:** `example://document-ocr-extract`, `example://file-to-structured`, + `example://json-to-excel`. + +## 2. Sortér en bunke dokumenter + +Upload mange blandede dokumenter, lad platformen klassificere hvert enkelt i en +kategori, du definerer, og træk valgfrie felter ud pr. kategori. + +- **Byggeklodser:** Sortér dokumenter (én output-kanal pr. kategori) → fx JSON til + Excel og/eller Sprogmodel (opsummering) pr. kategori. +- **Brug den til:** at rydde op i en blandet mappe af bilag, hvor hvert dokument + skal i den rigtige kasse. +- **MCP-eksempel:** `example://sort-documents`. + +## 3. Behandl hvert element i en liste (løkke) + +Gør det samme for hvert element i en liste — fx kør en sprogmodel på hver række +i et regneark eller hvert dokument i en bunke. + +- **Byggeklodser:** (kilde, fx CSV-parser) → Løkke Start → behandling (fx + Sprogmodel) → Løkke Slut (samler resultaterne). +- **Brug den til:** at anvende samme analyse på mange rækker, filer eller opslag. +- **MCP-eksempel:** `example://csv-enrich`. + +## 4. Betinget forgrening + +Send flowet ned ad forskellige veje afhængigt af en værdi. + +- **Byggeklodser:** Hvis / ellers (to grene, styret af en struktureret `config` — + ikke en sætning) eller Forgrening (flere grene efter værdi-match). Begge grene + føres typisk videre til samme næste trin — kun den gren, der matcher, giver output. +- **Brug den til:** at behandle sager forskelligt efter type, beløb eller status. +- **MCP-eksempel:** `example://if-else-branching` (og `guide://flow-building` for + den fulde `config`-form — den er ikke oplagt). + +## Almindelige faldgruber + +- **Brug ikke en kode-byggeklods (`python-code`) som type-oversætter.** Skal en fil + laves om til tekst, brug Dokument-OCR; skal tekst laves til JSON, brug Sprogmodel + med et `response_schema`. +- **Hvis / ellers** styres af en struktureret `config`, ikke en fritekst-sætning, + og begge grene (sand/falsk) skal føres videre. +- **Løkke-styring** (afbryd/spring over) hører til *inde* i en løkke. +- Skriv alt, brugeren ser (labels, instruktioner, skabelontekst, prompts), på dansk. diff --git a/agentbase-browser/skills/ret-et-flow/SKILL.md b/agentbase-browser/skills/ret-et-flow/SKILL.md new file mode 100644 index 0000000..f36f0e0 --- /dev/null +++ b/agentbase-browser/skills/ret-et-flow/SKILL.md @@ -0,0 +1,49 @@ +--- +name: ret-et-flow +description: Diagnosticerer og retter et eksisterende AgentBase-flow, der giver forkert, tomt eller fejlende output. Brug når brugeren siger "mit flow virker ikke", "det giver et forkert/tomt svar", "der kommer en fejl", "outputtet er tomt", eller vil fejlfinde/debugge/rette en agent, de allerede har bygget. Kræver at AgentBase-værktøjerne er tilsluttet. +--- + +# Ret et flow + +Når et flow giver et forkert eller tomt svar, så **byg det ikke om fra bunden.** +Reproducér fejlen på et rigtigt input, find den præcise byggeklods der fejler, +lav den *mindste* rettelse, og kør igen. Brugeren ser ikke lærredet (det er i en +anden fane), så vis det rigtige output i chatten undervejs. + +## Fejlfindingsløkken + +1. **Reproducér på et rigtigt input.** Kør flowet på præcis det input, der driller, + og se det *faktiske* output — ikke det, du regner med, det giver. Har du ikke et + input, der fejler, så bed brugeren om et. +2. **Find fejlen.** Læs kørslen med `get_execution` og find den byggeklods og det + håndtag (`node:handle`), der producerede forkert, tomt eller fejlet output. Gå + baglæns fra symptomet — den første node, der er gal, er årsagen; resten er følger. +3. **Lav den mindste ændring.** Ret kun det. Bekræft håndtagene med `get_node_type`, + og skriv grafen med `set_flow`. Udvid ikke flowet ud over dets formål. +4. **Kør igen på samme input.** Bekræft, at rettelsen virker. Gentag, til det er + pålideligt — stop ikke, bare fordi det er gemt. + +## Almindelige årsager — tjek disse først + +| Symptom | Årsag | Rettelse | +|---|---|---| +| Tomt svar / "Intet indhold angivet" efter en Hvis/ellers | Grenen er tom — `config` mangler `true_output`/`false_output` | Sæt begge i `config`'en, og før både sand- og falsk-grenen videre til næste trin | +| Rå `{pladsholder}` i output | Flere betingede grene samlet i én skabelon med flere variabler — kun den aktive gren udfyldes | Lad *hver* gren skrive den færdige tekst, og før dem til ÉT fælles felt (samme input-håndtag) | +| Fejl om filtype / "dataurl" vs. tekst | En fil er sendt direkte til et tekst-input | Sæt Dokument-OCR imellem — **aldrig** en kode-byggeklods (`python-code`) som type-oversætter | +| Kun første element behandlet / liste-fejl | En hel liste er sendt til en byggeklods, der forventer ét element | Pak behandlingen ind mellem Løkke Start og Løkke Slut | +| Løkke-styring virker ikke | Løkke Afbryd/Spring over ligger uden for løkken | Flyt dem ind på stien mellem Løkke Start og Løkke Slut | +| Sprogmodel giver fri tekst, hvor et system skal bruge faste felter | Manglende `response_schema` | Sæt et `response_schema` på Sprogmodel-byggeklodsen | + +Er årsagen ikke i tabellen, så lad `get_execution` pege på den — den viser hvert +trins input og output. `guide://flow-building` har den fulde `config`-form for +Hvis/ellers og type-reglerne. + +## Holdepunkter i chatten + +Vis brugeren det reproducerede, forkerte output og — efter rettelsen — det rette +output. De kan ikke se lærredet, så chatten er deres eneste indblik. Nævn åbent, +hvad du ændrede, og hvorfor. + +## Sprog + +Skriv alt, brugeren ser, på **dansk**. Node-id'er kan være korte ASCII. diff --git a/scripts/check-mcp-drift.sh b/scripts/check-mcp-drift.sh new file mode 100755 index 0000000..d602216 --- /dev/null +++ b/scripts/check-mcp-drift.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# Guards the two rot-prone facts the AgentBase browser packs depend on: the MCP +# endpoint host and its OAuth auth model. Real drift here is rare (~1 event/year) +# but silent and total — the server moves or changes auth under a still repo, and +# every packaged connector URL / skill instruction breaks at once. This asserts +# the live server still matches what the manifests and skills ship, and that the +# manifests haven't regressed to a static-token auth model. +# +# Exits non-zero with a "DRIFT: ..." line on any mismatch. Needs curl + jq. +set -euo pipefail + +# The single canonical MCP URL the manifests and skills quote. If the server +# moves, this constant and the shipped strings must both change together. +CANONICAL_URL="https://api.agentbase.dk/mcp" +# RFC 9728 Protected Resource Metadata for /mcp — root-mounted, unauthenticated. +WELL_KNOWN="https://api.agentbase.dk/.well-known/oauth-protected-resource/mcp" +# Dead host from an earlier era; must never reappear in the repo. +STALE_HOST="api.flows.syv.ai" + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PRM="$(mktemp)" +trap 'rm -f "$PRM"' EXIT + +fail() { echo "DRIFT: $*" >&2; exit 1; } + +echo "→ Fetching $WELL_KNOWN" +http_code="$(curl -sS -m 25 -o "$PRM" -w '%{http_code}' "$WELL_KNOWN")" \ + || fail "could not reach $WELL_KNOWN — host down, moved, or DNS gone" +[ "$http_code" = "200" ] || fail "well-known returned HTTP $http_code (expected 200)" +jq -e . "$PRM" >/dev/null 2>&1 || fail "well-known response is not valid JSON" + +# The server must still advertise /mcp at exactly the URL we ship. +resource="$(jq -r '.resource // empty' "$PRM")" +[ "$resource" = "$CANONICAL_URL" ] \ + || fail "advertised resource '$resource' != expected '$CANONICAL_URL' — endpoint moved" + +# OAuth must still be the auth model (RFC 9728 lists the authorization server(s)). +authz_count="$(jq '(.authorization_servers // []) | length' "$PRM")" +[ "$authz_count" -ge 1 ] \ + || fail "no authorization_servers advertised — OAuth no longer offered at $CANONICAL_URL" + +# The canonical URL must actually be what the repo ships (catches a stale constant +# above, or a manifest/skill that silently changed the URL). +grep -rqF "$CANONICAL_URL" "$REPO_ROOT" --include='*.json' --include='*.md' \ + --exclude-dir=.git \ + || fail "canonical URL $CANONICAL_URL is not present in any manifest or skill" + +# Per-manifest checks. The endpoint host and auth model can only really regress in +# a plugin.json — skill prose may legitimately NAME the dead host as forbidden, so +# scanning it repo-wide would false-positive. Two things a manifest must never do: +# 1. point at the dead host, or +# 2. carry a static-token auth block — OAuth is tokenless, so a userConfig prompt +# or an injected Authorization/headers block means we regressed to the old +# bearer-token model. (The server's own bearer_methods_supported is a normal +# OAuth field and is NOT checked here — only our manifests are.) +manifests=() +while IFS= read -r m; do manifests+=("$m"); done \ + < <(find "$REPO_ROOT" -path '*/.claude-plugin/plugin.json' -not -path '*/.git/*') +for m in "${manifests[@]}"; do + if grep -qF "$STALE_HOST" "$m"; then + fail "$m points at the dead host '$STALE_HOST' — use $CANONICAL_URL" + fi + if grep -qE '"(userConfig|headers|Authorization)"|Bearer ' "$m"; then + fail "$m carries a token-auth block (userConfig/headers/Authorization/Bearer) — should be OAuth-only" + fi +done + +echo "OK: MCP connection contract intact." +echo " resource = $resource" +echo " authorization_servers = $authz_count advertised" +echo " manifests checked = ${#manifests[@]} (no token-auth drift)"