What Can It Do — Write the List Down and the Client Belongs to One Server

5 min
GoalSecond in the mcp_client course. Ask for the tool and resource lists. Two lines, and whether they exist decides between "a client for that server" and "a client that stands in front of any server."

Part 1 attached. Now it asks.

Two lines

final tools = await client.listTools();
stdout.writeln('tools: ${tools.map((t) => t.name).join(", ")}');
for (final t in tools) {
  stdout.writeln('  ${t.name} — ${t.description}');
}

final resources = await client.listResources();
stdout.writeln('resources: ${resources.map((r) => r.uri).join(", ")}');
tools: desk.admit
  desk.admit — Admit a number of people from the queue
resources: ui://desk, desk://waiting

Write it down and it belongs to one server

You could write this instead.

// this server has desk.admit
await client.callTool('desk.admit', {'count': 1});

It runs. And at that moment this client belongs to that server. One tool name in the code and it cannot stand in front of anything else.

Asking is different. The list arrives at runtime, so the client does not need to know what it attached to. Attach to a board and led.set comes back; attach to the desk and desk.admit does. Same code.

This is where the description gets used

listTools() does not return names alone. The description and input schema come with them.

desk.admit — Admit a number of people from the queue

A human-facing client builds a button label and a tooltip from it. A model reads it to decide what to call. What server part 2 called "not a comment but running code" is consumed here.

Resources arrive as uris

resources: ui://desk, desk://waiting

ui:// is a screen and desk:// is a value. Not something the contract dictates — a naming rule the server chose. The client does not need to know it; in part 4 it just uses a uri from the list.

Verification — hardcoding fails

This piece claims the client asks rather than being told. So the check reads the client's code.

grep -q 'tools: .*desk.admit' captures/s2.txt || die "tools not discovered"
grep -q 'resources: .*ui://desk' captures/s2.txt || die "resources not discovered"

# The claim is that the client was not told; it asked.
grep -q "'desk.admit'" bin/step2.dart && die "the tool name is hardcoded in the client"

One occurrence of the tool name in bin/step2.dart and it fails.

Run it yourself

dart run bin/step2.dart
bash verify.sh

What to take

  1. listTools() and listResources() — the surface arrives at runtime
  2. Do not write names down — writing one makes it a single-server client
  3. A no-hardcoding check — stops this claim breaking quietly

Next

Call a tool. And learn that a refusal is not an exception.

Run the sample

cd course-client
dart pub get
dart run bin/step2.dart