You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4340c6b
Browse filesBrowse the repository at this point in the historyBrowse files
J["inject<br/>(you, in L3)"] -- "new login burst" --> K
23
23
S -- "StreamNative MCP<br/>sql_workspace_query" --> A["Orca agent<br/>hello-agent-<you>"]
24
24
A -- "sql_workspace_insert_rows<br/>(only if you approve)" --> F["SQL table<br/>flagged_accounts"]
25
25
```
26
26
27
27
| Step | Time | Where | You | The idea |
28
28
|---|---|---|---|---|
29
-
|[0. Connect](#step-0-connect-3-min)| 3 min | terminal | Fill in `.env`, run the doctor |One key, checked end to end|
29
+
|[0. Connect](#step-0-connect-3-min)| 3 min | terminal | Fill in `.env`, run the doctor |Check service access; authorize MCP with OAuth|
30
30
|[L1. Hello, agent](#l1-hello-agent-5-min)| 5 min | CLI / Python / TS | Create an agent and chat | Agent, environment, session, events |
31
31
|[L2. Hello, streaming SQL](#l2-hello-streaming-sql-8-min)| 8 min | SQL Workspace | Build a materialized view over the topic | Context that keeps itself fresh |
32
32
|[L3. Agent + live context](#l3-agent--live-context-9-min)| 9 min | CLI / Python / TS | Give the agent SQL tools, inject new data | The answer changes with the data |
@@ -54,8 +54,52 @@ Go to your path's folder and run the doctor:
54
54
| TypeScript |`cd typescript && npm run doctor`|
55
55
| CLI |`cd cli`, and run the doctor from your helper language: `(cd ../python && .venv/bin/python doctor.py)` or `(cd ../typescript && npm run doctor)`|
56
56
57
-
Every line should say `PASS`. A failed check prints its fix. Still stuck after
58
-
two tries? Raise your hand.
57
+
The service checks should say `PASS`. Before the first OAuth login, the MCP
58
+
check asks you to run L3; that script opens your browser and stores the credential
59
+
in a vault. After completing L2 and running L3, rerun the doctor to validate the
60
+
stored OAuth credential. This check verifies MCP initialization; L3/L4 exercise
61
+
the actual SQL tools. A failed check prints its fix. Still stuck after two tries?
62
+
Raise your hand.
63
+
64
+
For StreamNative SQL Workspace MCP, keep `SN_MCP_AUTH=oauth`, leave
65
+
`SN_MCP_OAUTH_ISSUER` empty for automatic discovery, and use the scope from
66
+
`.env.example`. Use an `ork` build containing [PR #8](https://github.com/orca-ae/orca-cli/pull/8)
67
+
or current main. Its discovery accepts HTTPS issuer aliases within the same
68
+
registrable domain and port. Only set `SN_MCP_OAUTH_ISSUER` when selecting one
69
+
of multiple advertised `authorization_servers`; copy that advertised value
70
+
exactly rather than the final issuer in authorization-server metadata.
71
+
`SN_API_KEY` authenticates the hosted Agent Engine,
72
+
Kafka and Schema Registry; it is not the OAuth MCP access token. All three paths
73
+
use `ork` for the first MCP login, then reuse the live credential for the same URL
74
+
and auth type from `.orca-state/<participant>.json`. Tokens stay in the server-side
75
+
vault, where they can be refreshed; they are never written to `.env` or local state.
76
+
Set `SN_MCP_AUTH=static_bearer` only when your MCP server accepts `SN_API_KEY`.
77
+
Changing the auth mode archives the previous live credential for that same URL
78
+
before creating its replacement (the Registry permits one active credential per
79
+
URL in a vault). If authorization fails, rerun L3/L4 to finish setup; other URLs'
80
+
credentials are preserved. A local Agent Engine with OAuth MCP needs only
81
+
`ORCA_API_KEY` for Registry authentication; `SN_API_KEY` is still needed for Kafka
82
+
and Schema Registry.
83
+
84
+
### Use a local Agent Engine
85
+
86
+
Start the CLI's stack with a provider key in your shell:
87
+
88
+
```bash
89
+
export ANTHROPIC_API_KEY='<your-provider-key>'
90
+
ork local start --with-gateway
91
+
```
92
+
93
+
Set `ORCA_BASE_URL=http://127.0.0.1:8080` in the tutorial's `.env`, and copy the
94
+
workspace key from the file printed by `ork local start` into `ORCA_API_KEY`.
95
+
The tutorial sends this key as `x-api-key`. A hosted team card continues to use
96
+
`SN_API_KEY` as a Bearer token when `ORCA_API_KEY` is empty.
97
+
98
+
For L1, run `python doctor.py --agent-only` or `npm run doctor -- --agent-only`.
99
+
This checks the Agent Engine without requiring Kafka, Schema Registry, or MCP.
100
+
The local stack provides the Agent Engine and AI Gateway; L2–L4 still need the
101
+
streaming data services from your team card. For L3/L4, keep `SN_API_KEY` set to
102
+
the MCP service key, separately from the local Registry's `ORCA_API_KEY`.
59
103
60
104
## L1: Hello, agent (5 min)
61
105
@@ -150,6 +194,19 @@ create a new version when its definition changes.
150
194
151
195
In the StreamNative Cloud console, open **SQL Workspace**, select the hackathon
152
196
workspace, and pick your team's database. Use a new query tab for each step.
197
+
The default Kafka topic is `security.login_events`; SQL Workspace exposes its
198
+
Avro source as `"avro.security.login_events"`.
199
+
200
+
**Align the SQL with your `.env` before running it.** The injectors and doctor use
201
+
`LOGIN_TOPIC`, but the SQL files and examples below contain a fixed source name:
202
+
SQL Workspace does not read your local `.env`. Check `LOGIN_TOPIC`, then replace
203
+
`"avro.security.login_events"` with `"avro.<your LOGIN_TOPIC>"` in both
204
+
[`sql/01_explore.sql`](sql/01_explore.sql) and
205
+
[`sql/02_login_failures.sql`](sql/02_login_failures.sql), and in any query copied
206
+
from this page. For example, `LOGIN_TOPIC=security.team07_logins` requires
207
+
`FROM "avro.security.team07_logins"`. Keep the double quotes around the entire
208
+
source name and confirm that SQL Workspace imported that topic as an Avro source.
209
+
Keep the `login_failures` view name: L3/L4 query that view.
153
210
154
211
**1. Peek at the stream** ([`sql/01_explore.sql`](sql/01_explore.sql)). Each row
155
212
is one login attempt. The topic name contains dots, so it's double-quoted.
@@ -228,8 +285,10 @@ event landed in Kafka, the view updated itself, and the agent read the view.
228
285
-`mcp_servers`: the StreamNative MCP server for your SQL Workspace.
229
286
-`tools`: an allow-list. Two read-only tools run without asking
230
287
(`always_allow`); every other tool on that server is disabled.
231
-
- A **vault**: the MCP server's credential (your team key) is stored server-side.
232
-
The session references the vault by id, so the key never enters the prompt.
288
+
- A **vault**: the MCP server's OAuth credential is created through `ork` and
289
+
stored server-side. Approve the browser login on the first run. The session
290
+
references the vault by id, so tokens never enter the prompt. Later runs reuse
291
+
the credential without another browser login.
233
292
234
293
<details>
235
294
<summary>The code (Python)</summary>
@@ -238,7 +297,7 @@ event landed in Kafka, the view updated itself, and the agent read the view.
The agent (version 3) gets one write tool, and it can only use it with your
295
-
approval. It queries the view, then proposes an insert, and the session pauses:
355
+
approval. It queries the view, describes the flag table, and reads the database
356
+
time before proposing an insert. The MCP insert tool requires every writable
357
+
column, including nullable columns; it does not apply table defaults. The
358
+
session pauses before the proposed row is written:
296
359
297
360
```
298
361
[approve?] The agent wants to run sql_workspace_insert_rows with:
299
362
{
363
+
"database": "<your database>",
364
+
"schema": "public",
300
365
"table": "flagged_accounts",
301
-
"rows": [{"account_id": "acct_9…", "reason": "6 failed logins then a success from one new IP"}]
366
+
"rows": [{
367
+
"account_id": "acct_9…",
368
+
"reason": "6 failed logins then a success from one new IP",
369
+
"flagged_at": "2026-09-30T12:00:00Z"
370
+
}]
302
371
}
303
372
Allow it? [y/N]
304
373
```
@@ -309,11 +378,12 @@ Type `y`, then check in SQL Workspace:
309
378
SELECT*FROM flagged_accounts;
310
379
```
311
380
312
-
Ask again, and answer `n` this time. The agent is told a human denied the insert,
381
+
Ask the agent to flag a different account, and answer `n` this time. The agent is told a human denied the insert,
313
382
and it does not retry.
314
383
315
-
**What changed** ([`agent/l4-act.json`](agent/l4-act.json)): one more tool,
316
-
`sql_workspace_insert_rows`, with `permission_policy: always_ask`. When the agent
384
+
**What changed** ([`agent/l4-act.json`](agent/l4-act.json)): the read-only
385
+
`sql_workspace_describe_table` checks the required columns, and
386
+
`sql_workspace_insert_rows` uses `permission_policy: always_ask`. When the agent
317
387
calls it, the session emits `agent.mcp_tool_use` and goes idle with
318
388
`stop_reason: requires_action`. Your script answers with a
319
389
`user.tool_confirmation`: `allow`, or `deny` with a reason. On the CLI that is:
@@ -341,9 +411,10 @@ action, and you have your hackathon project. Ideas and next steps:
341
411
| Doctor: `Agent Engine HTTP 401/403`| The key was rejected. A key created before its permissions must be re-created: ask a facilitator. |
342
412
| Doctor: `Kafka ... authentication`|`SN_SERVICE_ACCOUNT` must be the full principal, `<name>@<org>.auth.streamnative.cloud`; `SN_API_KEY` is the raw key. |
343
413
| The login topic isn't listed in SQL Workspace | Only topics with a registered Avro schema appear. Ask a facilitator. |
344
-
|`relation "avro.security.login_events" does not exist`| Select your team's database, and keep the double quotes around the name. |
414
+
|`relation "avro.security.login_events" does not exist`| Select your team's database and update the quoted Avro source in both L2 SQL files to match `LOGIN_TOPIC` in `.env`. |
345
415
| The agent can't find `login_failures`| Create the view in your team's database (L2, step 2); the agent looks it up there. |
346
-
|`[error]` lines from MCP tools in L3 | Check `SN_MCP_URL` against your team card, then rerun the doctor. |
416
+
|`[error]` lines from MCP tools in L3 | Check `SN_MCP_URL` and `SN_MCP_AUTH`, finish the OAuth login, then rerun the doctor. |
417
+
| OAuth issuer mismatch / unsupported client authentication | Use current `ork` main or PR #8 and leave `SN_MCP_OAUTH_ISSUER` empty for StreamNative discovery. An explicit issuer must match an advertised authorization server. `--oauth-allow-issuer-mismatch` is only for trusted servers whose metadata issuer crosses registrable domains; StreamNative does not need it. |
347
418
|`Cannot reach the Agent Engine`|`ORCA_BASE_URL` must be the host root from your card, with no `/v1`. |
348
419
| The agent answers from memory instead of querying | Ask again, "check the view first". The system prompt tells it to always query. |
349
420
@@ -353,8 +424,9 @@ action, and you have your hackathon project. Ideas and next steps:
353
424
|---|---|---|
354
425
|`./cleanup.sh`|`python cleanup.py`|`npm run cleanup`|
355
426
356
-
This archives your agent and deletes your vault and environment. To start L2
357
-
over, run [`sql/99_reset.sql`](sql/99_reset.sql).
427
+
This archives your agent and environment, and deletes your vault. An environment
428
+
with session history cannot be deleted; archiving keeps that history available.
429
+
To start L2 over, run [`sql/99_reset.sql`](sql/99_reset.sql).
Copy file name to clipboardExpand all lines: agent/l4-act.json
+7-6Lines changed: 7 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -1,19 +1,20 @@
1
1
{
2
2
"layer": "L4",
3
3
"summary": "+ insert into flagged_accounts, only with human approval",
4
-
"system": "You are a security analyst for Aegis Financial, a fictional bank. Your context is live: logins stream into Kafka, and a streaming SQL materialized view keeps a running summary in StreamNative SQL Workspace, which you query with the streamnative tools.\n\nRules:\n1. Always query before you answer. Never guess or reuse numbers from earlier answers: the data changes while you talk.\n2. First call sql_workspace_list_databases, then use the database that contains login_failures.\n3. login_failures has one row per account: account_id, failed_logins, successful_logins, distinct_ips, last_seen.\n4. Several failed logins (5 or more) plus at least one success is a likely account takeover.\n5. Cite the numbers you used. Keep answers under 120 words.\n\nActing:\n6. When asked to flag an account, insert exactly one row into the table flagged_accounts with sql_workspace_insert_rows: account_id, and reason (one sentence that cites the numbers).\n7. A human approves every insert. If an insert is denied, say so, and do not retry it.",
4
+
"system": "You are a security analyst for Aegis Financial, a fictional bank. Your context is live: logins stream into Kafka, and a streaming SQL materialized view keeps a running summary in StreamNative SQL Workspace, which you query with the streamnative tools.\n\nRules:\n1. Always query before you answer. Never guess or reuse numbers from earlier answers: the data changes while you talk.\n2. First call sql_workspace_list_databases, then use the database that contains login_failures.\n3. login_failures has one row per account: account_id, failed_logins, successful_logins, distinct_ips, last_seen.\n4. Several failed logins (5 or more) plus at least one success is a likely account takeover.\n5. Cite the numbers you used. Keep answers under 120 words.\n\nActing:\n6. When asked to flag an account, first call sql_workspace_describe_table for public.flagged_accounts. Every inserted row must include all writable columns, including nullable columns: this MCP tool does not apply table defaults. In this tutorial, supply account_id, reason (one sentence citing fresh login counts), and flagged_at. Read CURRENT_TIMESTAMP AS flagged_at with sql_workspace_query and use the returned RFC3339 timestamp as a literal; never invent a time or pass a SQL expression.\n7. Submit exactly one sql_workspace_insert_rows tool call with database, schema=public, table=flagged_accounts, and one complete row. Calling the tool proposes the action: Orca pauses it for human approval. Do not ask for chat approval before submitting the tool call.\n8. If approval is denied, say so and do not retry. A tool rejection is different from denied human approval. Check the tool outcome and visibility, and verify flagged_accounts with a read-only query after an accepted insert. Never automatically resubmit an insert after an error, warning, or unknown outcome.",
0 commit comments