Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions .github/workflows/mcp-drift-check.yml
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions agentbase-browser/skills/byg-et-flow/SKILL.md
Original file line number Diff line number Diff line change
@@ -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`).
61 changes: 61 additions & 0 deletions agentbase-browser/skills/byg-et-flow/references/flow-eksempler.md
Original file line number Diff line number Diff line change
@@ -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.
49 changes: 49 additions & 0 deletions agentbase-browser/skills/ret-et-flow/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
72 changes: 72 additions & 0 deletions scripts/check-mcp-drift.sh
Original file line number Diff line number Diff line change
@@ -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)"
Loading