Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e32a479
feat(week-05): move week-01 calculator and read_file into an MCP serv…
psyoung82 Sep 29, 2026
a3d6ca1
lab(week-05): call tools/list by hand over HTTP; missing Mcp-Method o…
psyoung82 Sep 29, 2026
3715902
feat(week-05): turn the week-01 loop into an MCP host; tools from too…
psyoung82 Sep 29, 2026
3998084
wip(week-05): stdio host run reached the model but failed with 402 in…
psyoung82 Sep 29, 2026
bd22942
lab(week-05): same question over stdio and HTTP, both read_file + cal…
psyoung82 Oct 5, 2026
be885af
lab(week-05): Claude Code as second client on the same HTTP server, r…
psyoung82 Oct 5, 2026
9eb6c9a
lab(week-05): add clock to the server only; unchanged host shows 3 to…
psyoung82 Oct 5, 2026
ad65f77
feat(week-05): scenarios.json (week-04's four, limits fixed before an…
psyoung82 Oct 5, 2026
aeda9e4
feat(week-05): market MCP server with bearer-token parties, turn chec…
psyoung82 Oct 5, 2026
e588a70
feat(week-05): auth_checks.txt from four live checks: 401 without tok…
psyoung82 Oct 5, 2026
db13688
feat(week-05): host (one turn, bearer header), runner (episodes, resu…
psyoung82 Oct 5, 2026
7ede86c
run(week-05): smoke episode, server_inject scenario 2 run 1, deal at …
psyoung82 Oct 5, 2026
7a73733
run(week-05): run 1 prompt_inject, 4 episodes: 2 deals inside limits,…
psyoung82 Oct 5, 2026
37b7265
run(week-05): run 1 server_inject complete, 4 episodes: 2 deals insid…
psyoung82 Oct 5, 2026
5be5a7f
run(week-05): run 2 prompt_inject, 4 episodes: 2 deals inside limits,…
psyoung82 Oct 5, 2026
17227ba
run(week-05): run 2 server_inject, 4 episodes: 2 deals inside limits,…
psyoung82 Oct 5, 2026
cf40035
run(week-05): run 3 prompt_inject, 4 episodes: 2 deals inside limits,…
psyoung82 Oct 5, 2026
007380e
run(week-05): run 3 server_inject, 4 episodes: 2 deals inside limits,…
psyoung82 Oct 5, 2026
5e8c6e0
docs(week-05): REPORT.md draft, four parts: setup, results, FIPA-ACL …
psyoung82 Oct 5, 2026
d0f02ab
docs(week-05): match the lecture's reference-run layout (week-04 comp…
psyoung82 Oct 5, 2026
0d3c9aa
docs(week-05): restructure the interpretation paragraph: claim first,…
psyoung82 Oct 5, 2026
4e05453
docs(week-05): state the negative result on the core question and wha…
psyoung82 Oct 5, 2026
28dec0f
docs(week-05): interpretation as one passage with three bullet lists …
psyoung82 Oct 5, 2026
3735c6c
misc: add stderr logs
psyoung82 Oct 5, 2026
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
256 changes: 256 additions & 0 deletions submissions/25622005/week-05/REPORT.md

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions submissions/25622005/week-05/auth_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Four checks against the running market server, one result line each -> auth_checks.txt.
Run: python auth_checks.py (market_server.py must be up on port 8001)"""
import asyncio
import json

import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client

BASE = "http://127.0.0.1:8001"
META = {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}}


async def call(tok, name, **args):
http = httpx2.AsyncClient(headers={"Authorization": f"Bearer {tok}"})
async with Client(streamable_http_client(f"{BASE}/mcp", http_client=http)) as c:
r = await c.call_tool(name, args)
return ("isError=true " if r.is_error else "") + " ".join(x.text for x in r.content if x.type == "text")


async def main():
lines = []
async with httpx2.AsyncClient() as h:
# (1) no token at all
r = await h.post(f"{BASE}/mcp", headers={"Content-Type": "application/json",
"Accept": "application/json, text/event-stream", "Mcp-Method": "tools/list"},
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {"_meta": META}})
lines.append(f"(1) no token: POST /mcp tools/list -> HTTP {r.status_code}, "
f"WWW-Authenticate: {r.headers.get('www-authenticate')}")
# two negotiations, server condition
a = (await h.post(f"{BASE}/admin/negotiations", json={"item": "a desk lamp", "reserve": 30,
"budget": 45, "condition": "server"})).json()
b = (await h.post(f"{BASE}/admin/negotiations", json={"item": "a used bicycle", "reserve": 120,
"budget": 150, "condition": "server"})).json()
A, B = a["negotiation_id"], b["negotiation_id"]
# (2) A's buyer token on negotiation B
out = await call(a["tokens"]["buyer"], "get_negotiation", negotiation_id=B)
lines.append(f"(2) party token on another negotiation: buyer token of {A} calls get_negotiation({B}) -> {out}")
# (3) seller moves while it is the buyer's turn
out = await call(a["tokens"]["seller"], "propose", negotiation_id=A, price=50)
lines.append(f"(3) move out of turn: seller of {A} calls propose(50) on the buyer's turn -> {out}")
# (4) server condition: buyer proposes above its token limit (budget 45)
out = await call(a["tokens"]["buyer"], "propose", negotiation_id=A, price=60)
lines.append(f"(4) outside the token limit (server condition, budget 45): buyer of {A} calls propose(60) -> {out}")
with open("auth_checks.txt", "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
print("\n".join(lines))

asyncio.run(main())
4 changes: 4 additions & 0 deletions submissions/25622005/week-05/auth_checks.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(1) no token: POST /mcp tools/list -> HTTP 401, WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", resource_metadata="http://127.0.0.1:8001/.well-known/oauth-protected-resource/mcp"
(2) party token on another negotiation: buyer token of neg_7dfac94e calls get_negotiation(neg_ee41e965) -> isError=true Error executing tool get_negotiation: your token is not a party to negotiation neg_ee41e965
(3) move out of turn: seller of neg_7dfac94e calls propose(50) on the buyer's turn -> isError=true Error executing tool propose: not your turn: it is the buyer's turn
(4) outside the token limit (server condition, budget 45): buyer of neg_7dfac94e calls propose(60) -> isError=true Error executing tool propose: refused: 60 is above your authorized budget of 45
88 changes: 88 additions & 0 deletions submissions/25622005/week-05/host.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""One turn of one party: the lab's week-01 loop as an MCP host, plus an
Authorization header. The agent reads the negotiation with get_negotiation and
makes one move. The host stops as soon as a move went through (the server
flipped the turn), or after MAX_STEPS model calls.

Model: Anthropic SDK, AGENT_MODEL from .env (no temperature argument, as in
week 04). Retries 429 and 5xx with a growing wait.
"""
import os
import time

import anthropic
import httpx2
from dotenv import load_dotenv
from mcp import Client
from mcp.client.streamable_http import streamable_http_client

load_dotenv()

MODEL = os.environ.get("AGENT_MODEL", "claude-sonnet-4-6")
MAX_TOKENS = 1024
MAX_STEPS = 6 # model calls per turn, a safety net
RETRIES, BACKOFF = 5, 5
MOVES = {"propose", "accept_proposal", "reject_proposal", "refuse"}


def to_anthropic(tool):
return {"name": tool.name, "description": tool.description,
"input_schema": tool.input_schema}


def _retryable(e: Exception) -> bool:
status = getattr(e, "status_code", None)
return status == 429 or (status is not None and status >= 500)


def call_model(client, system, tools, messages, log):
for attempt in range(RETRIES):
try:
return client.messages.create(model=MODEL, max_tokens=MAX_TOKENS, system=system,
tools=tools, messages=messages)
except Exception as e:
if not _retryable(e) or attempt == RETRIES - 1:
raise
wait = BACKOFF * 2 ** attempt
log(f" [{getattr(e, 'status_code', '?')}] waiting {wait}s, retry {attempt + 1}/{RETRIES - 1}")
time.sleep(wait)


async def take_turn(mcp_url: str, token: str, system: str, role: str, log=print) -> dict:
"""Returns {'tool_calls': n, 'moved': bool, 'model_calls': n}."""
client = anthropic.Anthropic()
http = httpx2.AsyncClient(headers={"Authorization": f"Bearer {token}"})
stats = {"tool_calls": 0, "moved": False, "model_calls": 0}
messages = [{"role": "user", "content": "It is your turn. Read the negotiation and make your move."}]

async with Client(streamable_http_client(mcp_url, http_client=http)) as mcp:
tools = [to_anthropic(t) for t in (await mcp.list_tools()).tools]

for step in range(MAX_STEPS):
resp = call_model(client, system, tools, messages, log)
stats["model_calls"] += 1
messages.append({"role": "assistant", "content": resp.content})
for b in resp.content:
if b.type == "text" and b.text.strip():
log(f" [{role} says] {b.text.strip()}")

if resp.stop_reason != "tool_use":
return stats

results = []
for block in resp.content:
if block.type != "tool_use":
continue
result = await mcp.call_tool(block.name, block.input)
stats["tool_calls"] += 1
out = "\n".join(c.text for c in result.content if c.type == "text")
tag = "REFUSED" if result.is_error else "ok"
log(f" [{role} tool] {block.name}({block.input}) -> {tag}: {out}")
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": out, "is_error": bool(result.is_error)})
if block.name in MOVES and not result.is_error:
stats["moved"] = True
messages.append({"role": "user", "content": results})
if stats["moved"]:
return stats # the move ended the turn; no need for a closing reply

return stats
25 changes: 25 additions & 0 deletions submissions/25622005/week-05/logs/claude-code-http-run-01.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[init] model=claude-fable-5-1 mcp_servers=[{'name': 'week01-tools', 'status': 'connected'}] tools=['mcp__week01-tools__calculator', 'mcp__week01-tools__read_file']
[text] I'll load the two MCP tool schemas, then read notes.txt and sum its numbers with the calculator.
[tool_use] ToolSearch({"query": "select:mcp__week01-tools__read_file,mcp__week01-tools__calculator", "max_results": 2})
[tool_result]
[tool_use] mcp__week01-tools__read_file({"path": "notes.txt"})
[tool_result] {"result":"Meeting memo, 2026-09-01\n\nAttendees: 4\nPizza budget: 48000\nDrinks: 9500\nReimbursed so far: 12000\n\nSum the numbers above to get the total the agent should report.\n"}
[tool_use] mcp__week01-tools__calculator({"expression": "4 + 48000 + 9500 + 12000"})
[tool_result] {"result":"69504"}
[text] The sum of the numbers in notes.txt is **69504**.

Using only the week01-tools MCP server:

- **read_file** returned the memo with four numbers: Attendees 4, Pizza budget 48000, Drinks 9500, Reimbursed so far 12000.
- **calculator** evaluated `4 + 48000 + 9500 + 12000` and returned 69504.

This matches the result recorded in your last lab commit for the stdio and HTTP runs.
[result] model=['claude-haiku-4-5-20251001', 'claude-fable-5-1'] turns=4 cost_usd=0.7544625
The sum of the numbers in notes.txt is **69504**.

Using only the week01-tools MCP server:

- **read_file** returned the memo with four numbers: Attendees 4, Pizza budget 48000, Drinks 9500, Reimbursed so far 12000.
- **calculator** evaluated `4 + 48000 + 9500 + 12000` and returned 69504.

This matches the result recorded in your last lab commit for the stdio and HTTP runs.
3 changes: 3 additions & 0 deletions submissions/25622005/week-05/logs/claude-code-mcp-add.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Added HTTP MCP server week01-tools with URL: http://127.0.0.1:8000/mcp to local config
File modified: /Users/soony-home/.claude.json [project: /Users/soony-home/seoul-tech-project/ai-agent-engineering-101/submissions/25622005/week-05]
week01-tools: http://127.0.0.1:8000/mcp (HTTP) - ✔ Connected
26 changes: 26 additions & 0 deletions submissions/25622005/week-05/logs/curl-tools-list.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### 1. full request: tools/list
HTTP/1.1 200 OK
date: Tue, 29 Sep 2026 11:44:43 GMT
server: uvicorn
content-length: 1008
content-type: application/json

{"jsonrpc":"2.0","id":2,"result":{"cacheScope":"private","resultType":"complete","tools":[{"description":"Evaluate an arithmetic expression such as '3 * (4 + 5)'. Numbers and + - * / ** only.","inputSchema":{"type":"object","properties":{"expression":{"title":"Expression","type":"string"}},"required":["expression"],"title":"calculatorArguments"},"name":"calculator","outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"calculatorOutput","type":"object"}},{"description":"Read a text file inside the server's working directory. Returns at most 4000 characters.","inputSchema":{"type":"object","properties":{"path":{"title":"Path","type":"string"}},"required":["path"],"title":"read_fileArguments"},"name":"read_file","outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"read_fileOutput","type":"object"}}],"ttlMs":0,"_meta":{"io.modelcontextprotocol/serverInfo":{"name":"week01-tools","version":""}}}}

### 2. without Mcp-Method header
HTTP/1.1 400 Bad Request
date: Tue, 29 Sep 2026 11:44:43 GMT
server: uvicorn
content-length: 119
content-type: application/json

{"jsonrpc":"2.0","id":2,"error":{"code":-32020,"message":"mcp-method header does not match the request body's method"}}

### 3. without _meta clientCapabilities
HTTP/1.1 400 Bad Request
date: Tue, 29 Sep 2026 11:44:43 GMT
server: uvicorn
content-length: 157
content-type: application/json

{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"params._meta is missing the required envelope key(s): io.modelcontextprotocol/clientCapabilities"}}
21 changes: 21 additions & 0 deletions submissions/25622005/week-05/logs/http-run-01.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[host] 2 tools: ['calculator', 'read_file']
[tool] read_file({'path': 'notes.txt'}) -> Meeting memo, 2026-09-01

Attendees: 4
Pizza budget: 48000
Drinks: 9500
Reimbursed so far: 12000

Sum the numbers above to get the total the agent should report.

[tool] calculator({'expression': '4 + 48000 + 9500 + 12000'}) -> 69504
The numbers in the memo are:

- Attendees: 4
- Pizza budget: 48000
- Drinks: 9500
- Reimbursed so far: 12000

**Sum: 4 + 48000 + 9500 + 12000 = 69,504**

(I excluded the date "2026-09-01" since it's a timestamp, not a quantity to be summed — let me know if you'd like that included instead.)
19 changes: 19 additions & 0 deletions submissions/25622005/week-05/logs/http-run-02-clock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[host] 3 tools: ['calculator', 'read_file', 'clock']
[tool] clock({}) -> 2026-10-05T13:09:54.096226+00:00 (unix=1791205794.096)
[tool] read_file({'path': 'notes.txt'}) -> Meeting memo, 2026-09-01

Attendees: 4
Pizza budget: 48000
Drinks: 9500
Reimbursed so far: 12000

Sum the numbers above to get the total the agent should report.

[tool] calculator({'expression': '4 + 48000 + 9500 + 12000'}) -> 69504
[tool] clock({}) -> 2026-10-05T13:09:55.678251+00:00 (unix=1791205795.678)
**Total of numbers in notes.txt:** 4 + 48000 + 9500 + 12000 = **69,504**

**Clock calls:**
- First call: 2026-10-05T13:09:54.096 UTC
- Second call: 2026-10-05T13:09:55.678 UTC
- **Elapsed time: ≈1.582 seconds**
Loading
Loading