An Introduction to Generative AI for RSEs

How we got here: principles and tooling — ICCS Summer School 2026

Cordero Core
Tom Meltzer
Matt Archer

In collaboration with

Institutions behind this session

University of Cambridge — Institute of Computing for Climate Science

University of Washington Scientific Software Engineering Center, University of Washington eScience Institute, University of Washington

Plan

  • How we got here: ~70 years of GenAI as a timeline
  • Inside a transformer: tokens, embeddings, attention, inference
  • From next-word prediction to assistants, agents, and MCP
  • Tooling overview and code demo
  • Opencode intro and configuration
  • Create your own tool call with MCP + Skills

How We Got Here

A winding road through computing history, from a 1950s cabinet computer with dials, past a beige workstation and GPU boards, to a modern laptop with a chat bubble and robotic arm.

  • Every concept in GenAI was an answer to a problem someone hit
  • So we’ll walk the timeline instead of a glossary
  • Destination: what is inside the tools you’ll use this afternoon

If You Remember Nothing Else

  1. Every AI model is the same machine.
  2. Your model is only as good as your harness.
  3. Generative AI tools are powerful but fragile.

Act I

1958 – 2012

Learning from data

1958: The Perceptron

1958 1969 1986 2012 2017 2020 2022 2024 now

The 1958 machine you’re still using

  • Inputs, weights, sum, threshold — no layers, no magic
  • Weights are dials: learn by nudging them on every mistake
  • Geometrically: learning to draw a line between two classes
  • Still the irreducible unit of every model you use

%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 25}, "theme": "base", "themeVariables": {"edgeLabelBackground": "#ffffff"}}}%%
flowchart LR
    x1([x₁]) -->|w₁| S["Σ"]
    x2([x₂]) -->|w₂| S
    x3([x₃]) -->|w₃| S
    S --> T["> θ ?"]
    T --> O(["0 / 1"])

    classDef io   fill:#6c757d,stroke:#495057,color:#fff
    classDef key  fill:#003b6f,stroke:#001f3f,color:#fff
    classDef step fill:#4b9cd3,stroke:#2c6e9e,color:#fff

    class x1,x2,x3,O io
    class S key
    class T step

1969: The Wall

1958 1969 1986 2012 2017 2020 2022 2024 now

The book that almost killed neural networks

  • Minsky & Papert prove a single-layer perceptron cannot compute XOR
  • The proof was correct — and still is
  • The lethal move was extrapolation: multi-layer networks declared “sterile”
  • Funding dried up: the first AI winter

A brick wall blocks a winding path, but a small door in the wall stands ajar with light shining through; a heavy closed book leans against the wall.

1986: Backpropagation

1958 1969 1986 2012 2017 2020 2022 2024 now

One wrong answer, a billion dials — how a network learns who to blame

  • Loss function: measures model error
  • Gradient descent: moves weights down the loss surface
  • Backpropagation: blame flows backward, layer by layer (chain rule)
  • Overfitting: memorises training data, fails to generalise

1986: The Training Loop

%%{init: {"flowchart": {"nodeSpacing": 15, "rankSpacing": 18}, "theme": "base", "themeVariables": {"edgeLabelBackground": "#ffffff"}}}%%
flowchart TD
    A([Training data]) --> B["Forward pass"]
    B --> C[Compute loss]
    C --> D[Backpropagation]
    D --> E[Update weights]
    E -->|repeat| B
    C -->|loss small enough| F([Done ✓])

    classDef io     fill:#6c757d,stroke:#495057,color:#fff
    classDef step   fill:#4b9cd3,stroke:#2c6e9e,color:#fff
    classDef key    fill:#003b6f,stroke:#001f3f,color:#fff

    class A,F io
    class B,C step
    class D,E key

1989–2006: Winters and Stubborn Ideas

1958 1969 1986 90s 2012 2017 2022 now

  • 1989 — LeCun’s convolutional network reads real ZIP codes: structure is knowledge
  • 1990s — SVMs win on clean math and guarantees; deep nets stall on the vanishing gradient
  • 2006 — Hinton’s layer-wise pretraining revives depth, under a new name: deep learning

2012: AlexNet — The Year the Dam Broke

1958 1969 1986 90s 2012 2017 2022 now

26%2011 best, top-5 error 15%AlexNet, 2012 · 2gaming GPUs

  • Nothing new in the network — the world finally supplied data + compute
  • ReLU dodges the vanishing gradient; dropout tames overfitting
  • “Just make it bigger” is born; scale becomes the moat

Act II

2013 – 2020

The machine learns to read

The Next Problem: Language

  • Vision fell to scale — but language resisted
  • An image is a grid, all present at once
  • A sentence is a sequence: “bank” needs memory of what came before
  • And first: how do you feed text to a machine that only does arithmetic?

LLMs: Tokenisation

LLMs cannot process raw text, it must first be converted to numbers.

  • Text is split into sub-word tokens using a learned vocabulary
  • Each token is assigned a unique integer ID
  • Common words are single tokens; rare words split into pieces

“I went to the”

235285 “I” 3806 “▁went” 576 “▁to” 573 “▁the”

2013: Meaning Becomes Geometry

word2vec: king − man + woman ≈ queen

The embedding matrix maps tokens to vectors; directions encode meaning.

A 3D vector space showing that the displacement from E(Japan) to E(Germany) approximately equals the displacement from E(Sushi) to E(Bratwurst), illustrating that directions in embedding space encode meaning.

Attention enriches each vector with context: bank (river) vs bank (finance)

3Blue1Brown, Deep Learning Ch. 5

2014: Attention — The Bottleneck

1958 1986 2012 2014 2017 2020 2022 now

Stop making the machine work from memory — let it look back

  • RNNs read one token at a time, carrying a running “mental note”
  • seq2seq: the whole sentence crushed into one fixed-size vector
  • Like translating a paragraph from a single sticky note
  • Bahdanau 2014: keep everything, let the decoder look back with learned soft weights
  • But attention was bolted onto RNNs — sequential, GPUs sitting idle

A long scroll of text is squeezed through a funnel, producing a single tiny sticky note that a confused reader stares at.

2017: Attention Is All You Need

1958 1986 2012 2014 2017 2020 2022 now

The 2017 paper hiding inside every AI you use

  • Eight Google researchers, writing a translation paper
  • The reckless move: keep attention, throw away the RNN
  • Every token attends to every other token — in parallel
  • Parallel means GPUs saturate; GPUs saturating means scale

Six word blocks arranged in a circle, every block connected to every other by thin lines, with one connection highlighted — every token attending to every other token.

Transformers: Inference I

Transformers: Inference I

Convert text to tokens

Transformers: Inference I

Convert text to tokens

Predict new tokens

Transformers: Inference I

Convert text to tokens

Predict new tokens

Convert tokens to text

Transformers: Inference II

Transformers: Inference II

“I went to the”

I ▁went ▁to ▁the

Transformers: Inference II

“backpropagation”

▁back prop agation

3 tokens

Transformers: Inference II

Transformers: Inference II

Transformers: Inference II

Token ID Embedding (2048 dims)
I 235285 [ 0.21, -0.83, 0.54, 0.12, … ]
▁went 3806 [ -0.44, 0.31, 0.09, -0.77, … ]
▁to 576 [ 0.67, 0.02, -0.51, 0.38, … ]
▁the 573 [ 0.55, -0.19, 0.73, -0.02, … ]

Transformers: Inference II

Transformers: Inference II

transformer block

Transformers: Inference II

transformer block

Self-attention

I went to the

“I” ↔︎ “went”: subject-verb   |   “went” → “to”: verb-preposition

Self-attention

“The bank by the river was steep”

“bank” attends strongly to “river” - meaning is of a riverbank, not financial

Transformers: Inference II

Transformers: Inference II

▁the → [ 0.55, -0.19, 0.73, … ]

× unembedding matrix (2048 × 256k)

→ logits for every token in vocab

→ softmax → sample

Token Probability
▁library 31%
▁store 18%
▁park 12%
▁doctor 8%

Only “the” matters — contextualised by attention.

Transformers: Inference II

Transformers: Inference II

Transformers: Inference II

[235285] [3806] [576] [573] → 4376 “▁library”

[235285] [3806] [576] [573] [4376] → 736 “▁this”

full sequence fed back in each loop

Transformers: Inference II

Transformers: Inference II

[235285, 3806, 576, 573, 4376, 736]

↓ vocab lookup

“I went to the library this”

just a lookup table, the inverse of tokenisation

Transformers: Summary

  • Tokenise: text → sub-word token IDs
  • Embed: token IDs → dense vectors (static meaning)
  • Self-attention: enrich each vector with context (dynamic meaning)
    • MLP × N layers: transform representations
  • Predict & sample: last token’s vector × unembedding matrix → next token ID
  • Autoregressive loop: append token, feed full sequence back in
  • Decode: token IDs → text (lookup table)

Transformers: Summary

  • The model is completely stateless
  • All context is in the text fed to it, there is no memory
  • Each forward pass re-processes the full sequence
  • Longer contexts are more expensive: attention is O(n²)

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))
  • huggingface_hub / transformers: The platform and library where the ML community collaborates on models, datasets, and applications.

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))
  • huggingface_hub / transformers: The platform and library where the ML community collaborates on models, datasets, and applications.
  • Login: Register with Hugging Face and obtain a key to download hosted models.

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))
  • huggingface_hub / transformers: The platform and library where the ML community collaborates on models, datasets, and applications.
  • Login: Register with Hugging Face and obtain a key to download hosted models.
  • Load tokenizer & model: Downloads Google Gemma-2b and its matching tokenizer.

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))
  • huggingface_hub / transformers: The platform and library where the ML community collaborates on models, datasets, and applications.
  • Login: Register with Hugging Face and obtain a key to download hosted models.
  • Load tokenizer & model: Downloads Google Gemma-2b and its matching tokenizer.
  • Tokenise input: Converts your text into a tensor of token IDs the model can read.

LLM Hello World

import os
from huggingface_hub import login
from transformers import AutoTokenizer, AutoModelForCausalLM

login(token=os.environ["HF_API_KEY"], add_to_git_credential=True)

tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b")

model = AutoModelForCausalLM.from_pretrained("google/gemma-2b")

input_text = "I went to the"
input_ids = tokenizer(input_text, return_tensors="pt")

outputs = model.generate(**input_ids, max_new_tokens=10, do_sample=True, top_p=0.9)
print(tokenizer.decode(outputs[0]))
  • huggingface_hub / transformers: The platform and library where the ML community collaborates on models, datasets, and applications.
  • Login: Register with Hugging Face and obtain a key to download hosted models.
  • Load tokenizer & model: Downloads Google Gemma-2b and its matching tokenizer.
  • Tokenise input: Converts your text into a tensor of token IDs the model can read.
  • Generate & decode: Model predicts tokens autoregressively; tokenizer converts them back to text.

Act III

2018 – now

From prediction to agents

2018: The Internet as a Textbook

1958 2012 2017 2018 2020 2022 2024 now

117MGPT-1, 2018 1.5BGPT-2, 2019

  • Take half the transformer, train it on one objective: predict the next token
  • The text is its own answer key — no human labelling, so it scales to the internet
  • Sounds too dumb to matter — but “The capital of France is ___” requires the fact

A robot sits reading an enormous open book whose pages are made of tiny webpage wireframes, with a stack of more giant books behind it.

2020: Scaling Laws

Bigger was the whole idea

  • Plot loss vs. parameters, data, compute → a straight line on log-log axes
  • You can forecast a model’s quality before you build it
  • “The graph turned a leap of faith into a line item”
  • GPT-3: 175B params — new skills appear that nobody trained in

Nov 2022: ChatGPT

1958 2012 2017 2018 2020 2022 2024 now

The week everyone found out — the model wasn’t new, the manners were

  • GPT-3 had been sitting in an API for two years
  • RLHF: (1) fine-tune on good answers, (2) humans rank outputs → reward model, (3) optimise against learned taste
  • Same 1986 training loop — only the blame signal changed

1Musers in 5 days 100Musers in 2 months

A human hand ranks three answer cards into podium order while a small robot in a chat bubble watches and learns.

2023: The Weights Get Out

The year AI escaped the API

  • Feb 2023: Meta’s Llama weights leak within a week of release
  • Open weights ≠ open source: you get the baked cake, not the recipe
  • Quantisation (llama.cpp): frontier-class models on a laptop
  • This afternoon you’ll use self-hosted models — this is why that’s possible

Hands pass a finished three-layer cake through an open door while the recipe book stays locked in a cabinet — open weights without the recipe.

The Goldfish Problem, Fix #1 — RAG

Giving a frozen model an open book

  • The model is frozen and stateless — and it bluffs (plausible ≠ correct)
  • Fine-tuning is the wrong fix: knowledge dissolves into the weights like sugar into water
  • RAG: chunk → embed → store; at question time retrieve top-k and put it in the context
  • Don’t change the model — change what it can see

A goldfish in a bowl sits at an exam desk beside a large open reference book — the frozen model taking an open-book exam.

2023: Tool Use — The Chatbot Grows Hands

The model never runs anything. It writes the ticket.

  1. You hand the model a menu of tools
  2. It emits a structured request — data, not prose: get_weather(city="Cambridge")
  3. Your harness runs the real function
  4. The result goes back into the context; the model writes the answer

A chef robot writes an order ticket and clips it to the kitchen rail; hands on the other side of the pass take the ticket to the stove — the model writes the ticket, the harness cooks.

2023–24: The Agent Loop

An agent is scaffolding and a loop around an LLM

  • LLM: The reasoning engine. It can plan, evaluate, or decide whether to “act” or “answer.”
  • System Prompt: Defines the persona, available tools, and operational boundaries.
  • Working memory: Maintains the state, including history, tool outputs, and the current goal.
  • Tools: External capabilities like web search, code execution, APIs, or connecting to RAG.

The Agent Loop

%%{init: {"flowchart": {"nodeSpacing": 40, "rankSpacing": 80}, "theme": "base", "themeVariables": {"edgeLabelBackground": "#ffffff"}}}%%
flowchart LR
    S([System prompt]) --> C
    H([Prompt]) --> C["Context window"]
    C --> L["LLM: generate text"]
    L --> D{Tool call?}
    D -->|yes| T["Tools (incl. RAG)"]
    T -->|result appended| C
    D -->|no| O([Output])

    classDef io      fill:#6c757d,stroke:#495057,color:#fff
    classDef core    fill:#003b6f,stroke:#001f3f,color:#fff
    classDef support fill:#4b9cd3,stroke:#2c6e9e,color:#fff
    classDef decision fill:#e9c46a,stroke:#f4a261,color:#000

    class S,H,O io
    class L core
    class C,T support
    class D decision

Nov 2024: MCP — A Universal Plug for Agents

1958 2012 2017 2020 2022 2024 now

  • The mess: every agent × every tool = a bespoke, brittle connector (N×M)
  • MCP: one open protocol between agents and tools (N+M)
  • Like USB for peripherals — or LSP for editors
  • Servers expose tools (act), resources (read), prompts (templates)

Left: a chaotic tangle of cables between devices. Right: the same devices connected neatly through one central hub — N times M collapses to N plus M.

2024–25: Reasoning Models

The machine that learned to think first

  • Old knob: more compute at training time
  • New knob: more compute at inference time — let it think per question
  • Chain-of-thought as scratch space, baked in via RL on verifiable answers
  • o1 (Sept 2024), DeepSeek-R1 (Jan 2025, open weights)

A robot works through a problem on a big sheet of scratch paper covered in diagrams, a lightbulb glowing overhead and a crumpled first attempt on the floor.

2025: Coding Agents

1958 2012 2017 2020 2022 2024 now

Where the whole timeline lands — and where this afternoon begins

  • Autocomplete (2021) → chat in the sidebar (2022) → it just goes and does it (2024+)
  • One tool = transformer + next-token + RLHF + tool use + agent loop + MCP
  • The harness is the other half: if the model is the brain, the harness is the body
  • The new cost: verification — code that looks right and isn’t, just as fast

The Timeline, In One Slide

1958 Perceptron learning by nudging dials
1986 Backprop (after the first winter) learning who to blame
2012 AlexNet (after the second) structure is knowledge; then scale wins
2017 Transformer attention, read in parallel
2020 GPT-3 + scaling laws the internet as a textbook
2022 ChatGPT (RLHF) the interface was the invention
2023 Open weights, RAG, tool use the chatbot grows hands
2024 Agent loop, MCP the loop + a universal plug
2025 Reasoning models, coding agents think first; meet the work

Self Study Resources

Further Reading

Foundational concepts and Transformers

Further Reading

Reinforcement Learning & Alignment

Gen-AI Concerns

  • Trust
  • Safety
  • Ethical
  • Environmental

Trust

Capability outran verification

  • Benchmarks are proxies — and Goodhart applies: a measure made a target stops measuring
  • Benchmark contamination: the exam leaks into the training data
  • The weights are grown, not written — interpretability is still a young microscope
  • The posture: calibrated trust — check the diff, not the green tick

Safety

And these are just from a software perspective…

Ethical

Environmental

Opinion

  • GenAI usage has parallels to HPC
  • If genAI can help science – I want to make it:
    • greener
    • safer
    • more ethical

Tools and Workflows

Opencode (CLI)

In this half of the training we will make use of opencode

Concepts can also be applied to similar tools e.g., VSCode, GitHub Copilot CLI etc.

Opencode Installation

Installation instructions here

  • Linux
curl -fsSL https://opencode.ai/install | bash
  • Mac
brew install anomalyco/tap/opencode
  • Windows (download .exe)

Opencode Configuration

We now need to configure opencode to run self-hosted LLMs

  1. Add API key to .basrhc (or equivalent) e.g.,
    export CAMLLM_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXX
  2. Configure opencode (see next slide)

Note

If you already have access to UoC’s LiteLLM (https://llm.hpc.cam.ac.uk) you can create one from the virtual keys page: Virtual Keys \(\rightarrow\) Create New Key.

Setting the API Key

Add the key to your shell profile so it’s available every session:

  • macOS (zsh):
echo 'export CAMLLM_API_KEY="your-key-here"' >> ~/.zshrc
source ~/.zshrc
  • Linux (bash):
echo 'export CAMLLM_API_KEY="your-key-here"' >> ~/.bashrc
source ~/.bashrc
  • Windows (PowerShell):
[Environment]::SetEnvironmentVariable("CAMLLM_API_KEY", "your-key-here", "User")

Note

Remember to replace "your-key-here" with your actual key.

Where is the Config File?

The opencode config lives at:

  • Mac / Linux: ~/.config/opencode/opencode.json
    • If $XDG_CONFIG_HOME is set, it uses $XDG_CONFIG_HOME/opencode/opencode.json instead
  • Windows: %USERPROFILE%\.config\opencode\opencode.json
    • This is .config in your user profile, not %APPDATA%
    • Run opencode once to auto-create the directory, then check with dir %USERPROFILE%\.config\opencode

Opencode Configuration

  • edit/create ~/.config/opencode/opencode.json
~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "cam-llm": {
      "options": {
        "baseURL": "https://llm.science.ai.cam.ac.uk/v1",
        "apiKey": "{env:CAMLLM_API_KEY}"
      },
      "models": {
        "Qwen/Qwen3.6-27B-FP8": {
          "name": "Qwen/Qwen3.6-27B-FP8",
          "modalities": { "input": ["text", "image"], "output": ["text"] }
        },
      }
    }
  },
  "permission": {
    "bash": { "*": "ask" },
    "edit": { "*": "allow" }
  }
}

Context Engineering

  • LLMs are powerful, but suffer from context bloat
  • Context window is finite resource
  • LOTR + Hobbit ~ 750k tokens / 100k LOC ~ 1M tokens
Model Name Context Size
Claude 4.6 Opus 1M
Gemini 3.1 Pro 1M – 10M
GPT-5.3-Codex 400k
Devstral-2-123B-Instruct-2512 256k

Solution

To resolve this issue, Anthropic open-sourced 2 methods:

MCP

  • Open-source standard
  • Connect LLMs to external systems

MCP examples

For example, opencode supports 11 built-in skills (see docs)

Note

LLMs can answer questions, but cannot interact with your system.

MCP example (add)

We will build our own using fastMCP

mcp-numbers.py
from fastmcp import FastMCP

mcp = FastMCP(name="mcp-numbers")

@mcp.tool
def add(a: int, b: int) -> int:
  """Add two numbers"""
  return a + b

if __name__ == "__main__":
  mcp.run()

MCP example (add)

  1. Now let’s add mcp-numbers to our opencode configuration
  2. Follow instructions in mcp/README.md
  3. Running /status in opencode should display
  4. Try Use mcp tool "numbers_add" to add 4 and -1

MCP examples (netcdf)

  • What about a more interesting example…
  • Can we give LLM power to inspect netcdf .nc files?
  • Let’s try with MCP.

MCP examples (netcdf)

  • Inspect file mcp/mcp-netcdf.py
../mcp/mcp-netcdf.py
# /// script
# dependencies = [
#   "netCDF4",
#   "fastmcp",
# ]
# ///

import netCDF4
from fastmcp import FastMCP

# Initialize the FastMCP server
mcp = FastMCP("nc-mcp")


@mcp.tool()
def get_variables(path: str) -> str:
    """
    Reads a NetCDF file from the given path and returns its variables.

    Args:
        path: The absolute or relative path to the NetCDF (.nc) file.

    Returns:
        A string representation of the NetCDF file's variables.
    """
    try:
        # Open the dataset
        dset = netCDF4.Dataset(path)

        # Capture the variables as a string to return to the client
        variables_output = ", ".join(dset.variables.keys())

        # Close the dataset to free up resources
        dset.close()

        return variables_output

    except FileNotFoundError:
        return f"Error: Could not find the file at path: {path}"
    except Exception as e:
        return f"Error reading NetCDF file: {str(e)}"


@mcp.tool()
def get_variable_shape(path: str, variable_name: str) -> dict | str:
    """
    Reads a NetCDF file from the given path and returns the shape of a specific
    variable.

    Args:
        path: The absolute or relative path to the NetCDF (.nc) file.
        variable_name: The name of the variable to get the shape for.

    Returns:
        A dictionary containing the shape of the specified variable.
        Example: {'temperature': (365, 180, 360)}
        Returns an error (as a string) if the variable is not found.
    """
    return dict()


if __name__ == "__main__":
    mcp.run()

MCP examples (netcdf)

  • mcp/mcp-netcdf.py contains 2 MCP tools
    • netcdf_get_variables
    • netcdf_get_variable_shape (to be implemented)
  • Try using netcdf_get_variables on file simple.nc

MCP examples (netcdf)

  • Implement netcdf_get_variable_shape
  • See stub in mcp/mcp-netcdf.py
../mcp/mcp-netcdf.py
@mcp.tool()
def get_variable_shape(path: str, variable_name: str) -> dict:
    """
    Reads a NetCDF file from the given path and returns the shape of a specific
    variable.
    ...
    """
    pass

(15 minutes for exercise)

Skills

  • Define reusable behavior via SKILL.md definitions
  • Agent skills let LLMs discover reusable instructions
  • Skills are loaded on-demand
  • Skills are “just” markdown files

Anatomy of a Skill

  • Many genAI tools support skills e.g., Claude code, opencode, codex etc.

Note

opencode requires that skills are stored in a specific set of locations (A full list can be found here). We will focus on these:

  • Project config: .opencode/skills/<name>/SKILL.md
  • Global config: ~/.config/opencode/skills/<name>/SKILL.md
<name>/               # Required: unique skill name
├── SKILL.md          # Required: instructions + metadata
├── scripts/          # Optional: executable code
├── references/       # Optional: documentation
└── assets/           # Optional: templates, resources

Skills Example (netcdf)

  • Let’s refactor our netcdf MCP tool as a skill
  • Follow instructions in skill/README.md:
cd project/root/GenAI-teaching
mkdir -p .opencode/skills/netcdf
ln -sf $(pwd)/skill/netcdf/SKILL.md .opencode/skills/netcdf/
  • Run /skills in opencode to check registration

Skills Example (netcdf)

skill/netcdf/SKILL.md
---
name: netcdf-processing
description: Use this skill for any operations involving NetCDF (.nc) files, including inspecting metadata, reading variable shapes, extracting data slices, or generating new NetCDF datasets.
---

# What I do

This skill provides guidance for inspecting and generating NetCDF files using
standard command-line utilities. Use these commands to understand dataset
structures before writing extraction scripts.

# When to use this skill

Use this skill whenever a user mentions climate data, multidimensional arrays,
.nc files, or atmospheric datasets.

## Workflow Decision Tree

- **Inspecting Schema**: Use `ncdump -h` first to understand dimensions.
- **Data Access**: If the file is large, only request specific variable slices (don't read entire arrays into context).
- **Creating Files**: Use `ncgen` for small CDL templates or `netCDF4` Python scripts for large datasets.

## Viewing Metadata with `ncdump`
`ncdump` is the standard tool for converting NetCDF binary files into
human-readable text (CDL format).

* **View Header Only (Recommended):** Displays dimensions, variables, and attributes without printing raw data.
    ```bash
    ncdump -h filename.nc
    ```
* **View Specific Variable:** Look at the data for a single variable (e.g., 'temperature').
    ```bash
    ncdump -v temperature filename.nc
    ```
* **Coordinate Formatting:** Use `-c` to see the header plus the values of coordinate variables (lat, lon, time).
    ```bash
    ncdump -c filename.nc
    ```

## Creating Files with `ncgen`
`ncgen` takes a text-based CDL file and compiles it into a binary `.nc` file.

* **Generate Binary from CDL:**
    ```bash
    ncgen -o output_file.nc input_text.cdl
    ```

Skills Example (netcdf)

Try running the following command

Note

Disable netcdf MCP server before trying to test the skill. They may conflict.

Skills Exercise

  • Create your own SKILL.md
  • Register it in opencode
  • Try using it

(15 minutes for exercise)

CLI

Do we really need MCP or Skills?…

CLI

  • Common CLI tools are already in model weights
  • No authentication
  • No configuration

CLI

  • Try asking model to run CLI commands
  • Don’t need to be specific e.g., “are there any untracked files in this repo?”
  • What issues do you foresee?

(5 minutes for exercise)

MCP vs CLI vs Skill

So how do I choose between MCP, CLI and Skills?

MCP Server CLI SKILL.md (Instruction)
Primary Purpose Tool calling – Need auth, permissions, audit trails, or remote access? Is data format important? Raw commands – Run terminal tools the model already knows (git, grep, docker). Domain Expertise – Provides workflows, rules, and domain knowledge.
Context/Loading Loaded immediately into context window (regardless of query) reducing effective context window size. Zero cost – Knowledge is baked into model weights; no schemas loaded. Lazy loaded when needed. Will still impact context window.
Timeout Timeout ~ 1-2 minutes. Ideal for short, quick function calls No timeout. No timeout.

Note

For a more in-depth comparison check out Cordero’s article “MCP vs CLI: What Your Agents Should Be Using”

Taking it further

  • opencode and other genAI tools often support agents/sub-agents (see docs)
  • Agents are specialized AI assistants that can be configured for specific tasks and workflows
  • They allow you to create focused tools with custom prompts, models, and tool access
  • More markdown 👀

Sub-Agent

  • Let’s create a sub-agent to generate PR messages
  • Use opencode agent create
  • Try creating your own
  • Modify it and see what difference it makes

(15 minutes for exercise)

Thanks for listening

University of Cambridge — Institute of Computing for Climate Science

University of Washington Scientific Software Engineering Center, University of Washington

References

Achiam, Joshua. 2018. Spinning up in Deep Reinforcement Learning. OpenAI. https://spinningup.openai.com.
DeepLearning.AI. 2024. Build and Train an LLM with JAX. Online course, DeepLearning.AI. https://learn.deeplearning.ai/courses/build-and-train-an-llm-with-jax/lesson/gy364z/introduction.
Hugging Face. 2022. Deep Reinforcement Learning Course. Online course. https://huggingface.co/learn/deep-rl-course.
Karpathy, Andrej. 2022. Neural Networks: Zero to Hero. YouTube playlist. https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ.
Sanderson, Grant. 2017. Neural Networks. YouTube playlist, 3Blue1Brown. https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi.
Stanford University. 2021. CS25: Transformers United. Stanford University Course. https://web.stanford.edu/class/cs25/.