@synapsor/runner 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +284 -13
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/runner.mjs +2982 -127
- package/docs/README.md +32 -54
- package/docs/getting-started-own-database.md +40 -8
- package/docs/http-mcp.md +200 -0
- package/docs/limitations.md +20 -0
- package/docs/local-mode.md +101 -2
- package/docs/mcp-audit.md +10 -4
- package/docs/mcp-client-setup.md +40 -1
- package/docs/security-boundary.md +17 -0
- package/examples/openai-agents-http/README.md +55 -0
- package/examples/openai-agents-http/agent.py +90 -0
- package/examples/openai-agents-http/requirements.txt +1 -0
- package/examples/openai-agents-stdio/README.md +62 -0
- package/examples/openai-agents-stdio/agent.py +70 -0
- package/examples/openai-agents-stdio/requirements.txt +1 -0
- package/examples/reference-support-billing-app/README.md +51 -0
- package/examples/reference-support-billing-app/schema.sql +14 -1
- package/examples/reference-support-billing-app/seed.sql +12 -5
- package/examples/reference-support-billing-app/synapsor.runner.json +105 -0
- package/package.json +3 -1
- package/docs/MCP_RUNNER_IMPLEMENTATION_PLAN.md +0 -187
- package/docs/architecture.md +0 -65
- package/docs/capability-config.md +0 -180
- package/docs/cloud-mode.md +0 -140
- package/docs/config-migrations.md +0 -67
- package/docs/demo-transcript.md +0 -73
- package/docs/dependency-license-inventory.md +0 -35
- package/docs/first-10-minutes.md +0 -147
- package/docs/licensing.md +0 -38
- package/docs/local-ui.md +0 -163
- package/docs/mcp-efficiency-benchmark.md +0 -84
- package/docs/operations.md +0 -38
- package/docs/own-db-20-minutes.md +0 -185
- package/docs/production-readiness.md +0 -39
- package/docs/protocol.md +0 -90
- package/docs/roadmap.md +0 -13
- package/docs/schema-inspection.md +0 -88
- package/docs/shadow-mode.md +0 -67
- package/docs/telemetry.md +0 -28
- package/docs/threat-model.md +0 -25
- package/docs/trusted-context.md +0 -70
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# OpenAI Agents SDK + Synapsor Runner over HTTP
|
|
2
|
+
|
|
3
|
+
This example shows an OpenAI Agents SDK app calling a long-running Synapsor
|
|
4
|
+
Runner HTTP MCP service.
|
|
5
|
+
|
|
6
|
+
Use HTTP when your agent runs as an app/server and should connect to Runner
|
|
7
|
+
over a local/private network endpoint instead of launching a stdio child
|
|
8
|
+
process.
|
|
9
|
+
|
|
10
|
+
Native HTTP MCP client support can vary by SDK version. This example is honest
|
|
11
|
+
about that: it uses the OpenAI Agents SDK for the agent and wraps Synapsor's
|
|
12
|
+
HTTP MCP JSON-RPC endpoint as an OpenAI function tool.
|
|
13
|
+
|
|
14
|
+
The model still sees a semantic action. It does not receive raw SQL, database
|
|
15
|
+
URLs, write credentials, approval tools, or commit tools.
|
|
16
|
+
|
|
17
|
+
## Terminal 1: Start Synapsor Runner HTTP MCP
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export DATABASE_URL="<postgres-or-mysql-read-url>"
|
|
21
|
+
export SYNAPSOR_TENANT_ID="acme"
|
|
22
|
+
export SYNAPSOR_PRINCIPAL="openai_agent_demo"
|
|
23
|
+
export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
|
|
24
|
+
|
|
25
|
+
npx -y -p @synapsor/runner@alpha synapsor mcp serve-http \
|
|
26
|
+
--config ./synapsor.runner.json \
|
|
27
|
+
--store ./.synapsor/local.db \
|
|
28
|
+
--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Terminal 2: Run The Agent
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m venv .venv
|
|
35
|
+
. .venv/bin/activate
|
|
36
|
+
pip install -r requirements.txt
|
|
37
|
+
|
|
38
|
+
export OPENAI_API_KEY="..."
|
|
39
|
+
export SYNAPSOR_RUNNER_HTTP_URL="http://127.0.0.1:8765/mcp"
|
|
40
|
+
export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
|
|
41
|
+
export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
42
|
+
|
|
43
|
+
python agent.py
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Expected behavior:
|
|
47
|
+
|
|
48
|
+
- the agent calls `billing.inspect_invoice` through Synapsor HTTP MCP;
|
|
49
|
+
- Synapsor applies trusted tenant/principal context from the server process;
|
|
50
|
+
- the response includes scoped data and evidence handles;
|
|
51
|
+
- no SQL/write/approval tool is exposed to the model;
|
|
52
|
+
- evidence/query audit are saved in the local Runner store.
|
|
53
|
+
|
|
54
|
+
For production-like deployment, keep HTTP MCP behind private networking/TLS,
|
|
55
|
+
bearer auth, and rate limits. See [HTTP MCP](../../docs/http-mcp.md).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import urllib.error
|
|
6
|
+
import urllib.request
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from agents import Agent, Runner, function_tool
|
|
10
|
+
except ImportError as exc:
|
|
11
|
+
raise SystemExit(
|
|
12
|
+
"This example requires the OpenAI Agents SDK. "
|
|
13
|
+
"Install with: pip install -r requirements.txt"
|
|
14
|
+
) from exc
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def call_synapsor_http_mcp(method: str, params: dict) -> dict:
|
|
18
|
+
url = os.environ.get("SYNAPSOR_RUNNER_HTTP_URL", "http://127.0.0.1:8765/mcp")
|
|
19
|
+
token = os.environ.get("SYNAPSOR_RUNNER_HTTP_TOKEN")
|
|
20
|
+
if not token:
|
|
21
|
+
raise RuntimeError("SYNAPSOR_RUNNER_HTTP_TOKEN is not set")
|
|
22
|
+
|
|
23
|
+
payload = json.dumps({
|
|
24
|
+
"jsonrpc": "2.0",
|
|
25
|
+
"id": 1,
|
|
26
|
+
"method": method,
|
|
27
|
+
"params": params,
|
|
28
|
+
}).encode("utf-8")
|
|
29
|
+
request = urllib.request.Request(
|
|
30
|
+
url,
|
|
31
|
+
data=payload,
|
|
32
|
+
method="POST",
|
|
33
|
+
headers={
|
|
34
|
+
"authorization": f"Bearer {token}",
|
|
35
|
+
"content-type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
try:
|
|
39
|
+
with urllib.request.urlopen(request, timeout=15) as response:
|
|
40
|
+
body = response.read().decode("utf-8")
|
|
41
|
+
except urllib.error.HTTPError as exc:
|
|
42
|
+
body = exc.read().decode("utf-8")
|
|
43
|
+
raise RuntimeError(f"Synapsor HTTP MCP returned HTTP {exc.code}: {body}") from exc
|
|
44
|
+
|
|
45
|
+
parsed = json.loads(body)
|
|
46
|
+
if "error" in parsed:
|
|
47
|
+
raise RuntimeError(f"Synapsor HTTP MCP error: {parsed['error']}")
|
|
48
|
+
return parsed["result"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@function_tool
|
|
52
|
+
def billing_inspect_invoice(invoice_id: str) -> str:
|
|
53
|
+
"""Inspect one scoped invoice through Synapsor Runner HTTP MCP."""
|
|
54
|
+
result = call_synapsor_http_mcp("tools/call", {
|
|
55
|
+
"name": "billing.inspect_invoice",
|
|
56
|
+
"arguments": {"invoice_id": invoice_id},
|
|
57
|
+
})
|
|
58
|
+
return json.dumps(result.get("structuredContent", result), indent=2)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def main() -> None:
|
|
62
|
+
required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "SYNAPSOR_RUNNER_HTTP_TOKEN"]
|
|
63
|
+
missing = [name for name in required if not os.environ.get(name)]
|
|
64
|
+
if missing:
|
|
65
|
+
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
66
|
+
|
|
67
|
+
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
68
|
+
agent = Agent(
|
|
69
|
+
name="Synapsor HTTP MCP demo agent",
|
|
70
|
+
instructions=(
|
|
71
|
+
"Use the billing_inspect_invoice tool to inspect scoped invoice data. "
|
|
72
|
+
"Do not claim that you can run SQL, approve proposals, or commit writes."
|
|
73
|
+
),
|
|
74
|
+
tools=[billing_inspect_invoice],
|
|
75
|
+
)
|
|
76
|
+
result = await Runner.run(
|
|
77
|
+
agent,
|
|
78
|
+
(
|
|
79
|
+
f"Inspect invoice {invoice_id} using Synapsor. "
|
|
80
|
+
"Explain what you saw and whether you have write authority."
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
print(result.final_output)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
try:
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
except KeyboardInterrupt:
|
|
90
|
+
sys.exit(130)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openai-agents>=0.1.0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# OpenAI Agents SDK + Synapsor Runner over stdio
|
|
2
|
+
|
|
3
|
+
This example shows an OpenAI Agents SDK app launching Synapsor Runner as a
|
|
4
|
+
local stdio MCP server.
|
|
5
|
+
|
|
6
|
+
Use stdio when the agent process can start the MCP server on the same machine.
|
|
7
|
+
The model sees Synapsor semantic tools such as `billing.inspect_invoice`. It
|
|
8
|
+
does not receive raw SQL, database URLs, write credentials, approval tools, or
|
|
9
|
+
commit tools.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Generate `synapsor.runner.json` first:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y -p @synapsor/runner@alpha synapsor demo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
or connect your own staging database:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx -y -p @synapsor/runner@alpha synapsor init --wizard --from-env DATABASE_URL --mode read_only
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then install the Python dependencies:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m venv .venv
|
|
29
|
+
. .venv/bin/activate
|
|
30
|
+
pip install -r requirements.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Run
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export OPENAI_API_KEY="..."
|
|
37
|
+
export DATABASE_URL="<postgres-or-mysql-read-url>"
|
|
38
|
+
export SYNAPSOR_TENANT_ID="acme"
|
|
39
|
+
export SYNAPSOR_PRINCIPAL="openai_agent_demo"
|
|
40
|
+
|
|
41
|
+
python agent.py
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Optional env:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export SYNAPSOR_CONFIG="./synapsor.runner.json"
|
|
48
|
+
export SYNAPSOR_STORE="./.synapsor/local.db"
|
|
49
|
+
export SYNAPSOR_TOOL="billing.inspect_invoice"
|
|
50
|
+
export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Expected behavior:
|
|
54
|
+
|
|
55
|
+
- the agent can inspect the scoped invoice through Synapsor;
|
|
56
|
+
- the agent cannot run SQL;
|
|
57
|
+
- the agent cannot approve or commit writes;
|
|
58
|
+
- evidence/query audit are saved in the local Runner store.
|
|
59
|
+
|
|
60
|
+
If your installed OpenAI Agents SDK does not expose `MCPServerStdio`, update the
|
|
61
|
+
SDK or use the HTTP example, which wraps Synapsor HTTP MCP with a small JSON-RPC
|
|
62
|
+
client.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from agents import Agent, Runner
|
|
7
|
+
from agents.mcp import MCPServerStdio
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise SystemExit(
|
|
10
|
+
"This example requires the OpenAI Agents SDK with MCP stdio support. "
|
|
11
|
+
"Install with: pip install -r requirements.txt"
|
|
12
|
+
) from exc
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def main() -> None:
|
|
16
|
+
config_path = os.environ.get("SYNAPSOR_CONFIG", "./synapsor.runner.json")
|
|
17
|
+
store_path = os.environ.get("SYNAPSOR_STORE", "./.synapsor/local.db")
|
|
18
|
+
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
19
|
+
|
|
20
|
+
required = ["OPENAI_API_KEY", "DATABASE_URL", "SYNAPSOR_TENANT_ID", "SYNAPSOR_PRINCIPAL"]
|
|
21
|
+
missing = [name for name in required if not os.environ.get(name)]
|
|
22
|
+
if missing:
|
|
23
|
+
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
24
|
+
|
|
25
|
+
params = {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": [
|
|
28
|
+
"-y",
|
|
29
|
+
"-p",
|
|
30
|
+
"@synapsor/runner@alpha",
|
|
31
|
+
"synapsor",
|
|
32
|
+
"mcp",
|
|
33
|
+
"serve",
|
|
34
|
+
"--config",
|
|
35
|
+
config_path,
|
|
36
|
+
"--store",
|
|
37
|
+
store_path,
|
|
38
|
+
],
|
|
39
|
+
"env": {
|
|
40
|
+
**os.environ,
|
|
41
|
+
"DATABASE_URL": os.environ["DATABASE_URL"],
|
|
42
|
+
"SYNAPSOR_TENANT_ID": os.environ["SYNAPSOR_TENANT_ID"],
|
|
43
|
+
"SYNAPSOR_PRINCIPAL": os.environ["SYNAPSOR_PRINCIPAL"],
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async with MCPServerStdio(params=params) as mcp_server:
|
|
48
|
+
agent = Agent(
|
|
49
|
+
name="Synapsor stdio MCP demo agent",
|
|
50
|
+
instructions=(
|
|
51
|
+
"Use Synapsor MCP tools to inspect scoped database data. "
|
|
52
|
+
"Do not claim that you can run SQL, approve proposals, or commit writes."
|
|
53
|
+
),
|
|
54
|
+
mcp_servers=[mcp_server],
|
|
55
|
+
)
|
|
56
|
+
result = await Runner.run(
|
|
57
|
+
agent,
|
|
58
|
+
(
|
|
59
|
+
f"Inspect invoice {invoice_id} using Synapsor. "
|
|
60
|
+
"Explain what you saw and whether you have write authority."
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
print(result.final_output)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
try:
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
except KeyboardInterrupt:
|
|
70
|
+
sys.exit(130)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openai-agents>=0.1.0
|
|
@@ -54,11 +54,62 @@ The model-facing MCP tools are:
|
|
|
54
54
|
|
|
55
55
|
- `support.inspect_ticket`
|
|
56
56
|
- `support.propose_ticket_resolution`
|
|
57
|
+
- `support.inspect_customer_account`
|
|
58
|
+
- `support.propose_plan_credit`
|
|
57
59
|
- `billing.inspect_invoice`
|
|
58
60
|
- `billing.propose_late_fee_waiver`
|
|
61
|
+
- `orders.inspect_order`
|
|
62
|
+
- `orders.propose_status_change`
|
|
59
63
|
|
|
60
64
|
The model does not receive approval tools, commit tools, write credentials, raw SQL, arbitrary table names, arbitrary column names, or tenant authority.
|
|
61
65
|
|
|
66
|
+
## Safe Write Examples
|
|
67
|
+
|
|
68
|
+
After `synapsor demo` or after starting this fixture manually, try the same
|
|
69
|
+
proposal-first loop without connecting an MCP client:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx -y -p @synapsor/runner@alpha synapsor propose billing.propose_late_fee_waiver --sample
|
|
73
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals show latest
|
|
74
|
+
npx -y -p @synapsor/runner@alpha synapsor proposals approve latest --yes
|
|
75
|
+
npx -y -p @synapsor/runner@alpha synapsor apply latest
|
|
76
|
+
npx -y -p @synapsor/runner@alpha synapsor replay latest
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Expected safety output:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Source DB changed:
|
|
83
|
+
no
|
|
84
|
+
|
|
85
|
+
Guarded writeback applied.
|
|
86
|
+
* proposal approved: yes
|
|
87
|
+
* primary key matched: yes
|
|
88
|
+
* tenant guard matched: yes
|
|
89
|
+
* allowed columns only: yes
|
|
90
|
+
* conflict guard passed: yes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Other proposal examples use the same review/apply/replay path:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx -y -p @synapsor/runner@alpha synapsor propose support.propose_plan_credit --sample
|
|
97
|
+
npx -y -p @synapsor/runner@alpha synapsor propose orders.propose_status_change --sample
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Safety guarantees:
|
|
101
|
+
|
|
102
|
+
- proposals store evidence, a before/after diff, trusted tenant/principal
|
|
103
|
+
context, and query audit before any write;
|
|
104
|
+
- approval stays outside the model-facing MCP tool surface;
|
|
105
|
+
- writeback is single-row, primary-key targeted, tenant guarded,
|
|
106
|
+
allowed-column checked, idempotent, and conflict guarded;
|
|
107
|
+
- stale rows return `conflict` and are replayable.
|
|
108
|
+
|
|
109
|
+
Current limitation: v0.1 is intentionally local and single-user. It does not
|
|
110
|
+
provide hosted RBAC, workflow DAGs, branches, settlement policies, auto-merge,
|
|
111
|
+
or production-scale runner orchestration.
|
|
112
|
+
|
|
62
113
|
## Review And Replay
|
|
63
114
|
|
|
64
115
|
After a proposal exists:
|
|
@@ -11,6 +11,8 @@ CREATE TABLE IF NOT EXISTS public.customers (
|
|
|
11
11
|
name text NOT NULL,
|
|
12
12
|
email text,
|
|
13
13
|
plan text NOT NULL,
|
|
14
|
+
plan_credit_cents integer NOT NULL DEFAULT 0,
|
|
15
|
+
credit_reason text,
|
|
14
16
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
17
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
16
18
|
);
|
|
@@ -36,6 +38,15 @@ CREATE TABLE IF NOT EXISTS public.invoices (
|
|
|
36
38
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
37
39
|
);
|
|
38
40
|
|
|
41
|
+
CREATE TABLE IF NOT EXISTS public.orders (
|
|
42
|
+
id text PRIMARY KEY,
|
|
43
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
44
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
45
|
+
status text NOT NULL,
|
|
46
|
+
status_change_reason text,
|
|
47
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
48
|
+
);
|
|
49
|
+
|
|
39
50
|
DO $$
|
|
40
51
|
BEGIN
|
|
41
52
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
|
|
@@ -50,6 +61,8 @@ $$;
|
|
|
50
61
|
GRANT CONNECT ON DATABASE synapsor_reference_support_billing TO synapsor_reader, synapsor_writer;
|
|
51
62
|
GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
|
|
52
63
|
GRANT CREATE ON SCHEMA public TO synapsor_writer;
|
|
53
|
-
GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices TO synapsor_reader, synapsor_writer;
|
|
64
|
+
GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices, public.orders TO synapsor_reader, synapsor_writer;
|
|
65
|
+
GRANT UPDATE (plan_credit_cents, credit_reason, updated_at) ON public.customers TO synapsor_writer;
|
|
54
66
|
GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
|
|
55
67
|
GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
|
|
68
|
+
GRANT UPDATE (status, status_change_reason, updated_at) ON public.orders TO synapsor_writer;
|
|
@@ -4,12 +4,12 @@ VALUES
|
|
|
4
4
|
('otherco', 'OtherCo Labs', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
5
5
|
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, updated_at = EXCLUDED.updated_at;
|
|
6
6
|
|
|
7
|
-
INSERT INTO public.customers (id, tenant_id, name, email, plan, created_at, updated_at)
|
|
7
|
+
INSERT INTO public.customers (id, tenant_id, name, email, plan, plan_credit_cents, credit_reason, created_at, updated_at)
|
|
8
8
|
VALUES
|
|
9
|
-
('cust_acme_1', 'acme', 'Acme Robotics', 'ops@example.invalid', 'enterprise', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
10
|
-
('cust_acme_2', 'acme', 'Acme Field Ops', 'field@example.invalid', 'builder', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
11
|
-
('cust_other_1', 'otherco', 'OtherCo Labs', 'ops@otherco.invalid', 'builder', '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
12
|
-
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, name = EXCLUDED.name, email = EXCLUDED.email, plan = EXCLUDED.plan, updated_at = EXCLUDED.updated_at;
|
|
9
|
+
('cust_acme_1', 'acme', 'Acme Robotics', 'ops@example.invalid', 'enterprise', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
10
|
+
('cust_acme_2', 'acme', 'Acme Field Ops', 'field@example.invalid', 'builder', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z'),
|
|
11
|
+
('cust_other_1', 'otherco', 'OtherCo Labs', 'ops@otherco.invalid', 'builder', 0, NULL, '2026-06-20T10:00:00Z', '2026-06-20T10:00:00Z')
|
|
12
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, name = EXCLUDED.name, email = EXCLUDED.email, plan = EXCLUDED.plan, plan_credit_cents = EXCLUDED.plan_credit_cents, credit_reason = EXCLUDED.credit_reason, updated_at = EXCLUDED.updated_at;
|
|
13
13
|
|
|
14
14
|
INSERT INTO public.support_tickets (id, tenant_id, customer_id, subject, status, resolution_note, updated_at)
|
|
15
15
|
VALUES
|
|
@@ -24,3 +24,10 @@ VALUES
|
|
|
24
24
|
('INV-3002', 'acme', 'cust_acme_2', 'paid', 0, 0, NULL, '2026-06-20T14:40:00Z'),
|
|
25
25
|
('INV-9001', 'otherco', 'cust_other_1', 'overdue', 25500, 5500, NULL, '2026-06-20T14:31:08Z')
|
|
26
26
|
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, customer_id = EXCLUDED.customer_id, status = EXCLUDED.status, balance_cents = EXCLUDED.balance_cents, late_fee_cents = EXCLUDED.late_fee_cents, waiver_reason = EXCLUDED.waiver_reason, updated_at = EXCLUDED.updated_at;
|
|
27
|
+
|
|
28
|
+
INSERT INTO public.orders (id, tenant_id, customer_id, status, status_change_reason, updated_at)
|
|
29
|
+
VALUES
|
|
30
|
+
('O-1001', 'acme', 'cust_acme_1', 'paid', NULL, '2026-06-20T13:00:00Z'),
|
|
31
|
+
('O-1002', 'acme', 'cust_acme_2', 'processing', NULL, '2026-06-20T13:05:00Z'),
|
|
32
|
+
('O-9001', 'otherco', 'cust_other_1', 'paid', NULL, '2026-06-20T13:00:00Z')
|
|
33
|
+
ON CONFLICT (id) DO UPDATE SET tenant_id = EXCLUDED.tenant_id, customer_id = EXCLUDED.customer_id, status = EXCLUDED.status, status_change_reason = EXCLUDED.status_change_reason, updated_at = EXCLUDED.updated_at;
|
|
@@ -83,6 +83,56 @@
|
|
|
83
83
|
"conflict_guard": { "column": "updated_at" },
|
|
84
84
|
"approval": { "mode": "human", "required_role": "support_lead" }
|
|
85
85
|
},
|
|
86
|
+
{
|
|
87
|
+
"name": "support.inspect_customer_account",
|
|
88
|
+
"kind": "read",
|
|
89
|
+
"source": "app_postgres",
|
|
90
|
+
"context": "local_operator",
|
|
91
|
+
"target": {
|
|
92
|
+
"schema": "public",
|
|
93
|
+
"table": "customers",
|
|
94
|
+
"primary_key": "id",
|
|
95
|
+
"tenant_key": "tenant_id"
|
|
96
|
+
},
|
|
97
|
+
"args": {
|
|
98
|
+
"customer_id": { "type": "string", "required": true, "max_length": 128 }
|
|
99
|
+
},
|
|
100
|
+
"lookup": { "id_from_arg": "customer_id" },
|
|
101
|
+
"visible_columns": ["id", "tenant_id", "name", "plan", "plan_credit_cents", "credit_reason", "updated_at"],
|
|
102
|
+
"evidence": "required",
|
|
103
|
+
"max_rows": 1
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "support.propose_plan_credit",
|
|
107
|
+
"kind": "proposal",
|
|
108
|
+
"source": "app_postgres",
|
|
109
|
+
"context": "local_operator",
|
|
110
|
+
"target": {
|
|
111
|
+
"schema": "public",
|
|
112
|
+
"table": "customers",
|
|
113
|
+
"primary_key": "id",
|
|
114
|
+
"tenant_key": "tenant_id"
|
|
115
|
+
},
|
|
116
|
+
"args": {
|
|
117
|
+
"customer_id": { "type": "string", "required": true, "max_length": 128 },
|
|
118
|
+
"credit_cents": { "type": "number", "required": true, "minimum": 0, "maximum": 5000 },
|
|
119
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
120
|
+
},
|
|
121
|
+
"lookup": { "id_from_arg": "customer_id" },
|
|
122
|
+
"visible_columns": ["id", "tenant_id", "name", "plan", "plan_credit_cents", "credit_reason", "updated_at"],
|
|
123
|
+
"evidence": "required",
|
|
124
|
+
"max_rows": 1,
|
|
125
|
+
"patch": {
|
|
126
|
+
"plan_credit_cents": { "from_arg": "credit_cents" },
|
|
127
|
+
"credit_reason": { "from_arg": "reason" }
|
|
128
|
+
},
|
|
129
|
+
"allowed_columns": ["plan_credit_cents", "credit_reason"],
|
|
130
|
+
"numeric_bounds": {
|
|
131
|
+
"plan_credit_cents": { "minimum": 0, "maximum": 5000 }
|
|
132
|
+
},
|
|
133
|
+
"conflict_guard": { "column": "updated_at" },
|
|
134
|
+
"approval": { "mode": "human", "required_role": "support_lead" }
|
|
135
|
+
},
|
|
86
136
|
{
|
|
87
137
|
"name": "billing.inspect_invoice",
|
|
88
138
|
"kind": "read",
|
|
@@ -131,6 +181,61 @@
|
|
|
131
181
|
},
|
|
132
182
|
"conflict_guard": { "column": "updated_at" },
|
|
133
183
|
"approval": { "mode": "human", "required_role": "billing_lead" }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "orders.inspect_order",
|
|
187
|
+
"kind": "read",
|
|
188
|
+
"source": "app_postgres",
|
|
189
|
+
"context": "local_operator",
|
|
190
|
+
"target": {
|
|
191
|
+
"schema": "public",
|
|
192
|
+
"table": "orders",
|
|
193
|
+
"primary_key": "id",
|
|
194
|
+
"tenant_key": "tenant_id"
|
|
195
|
+
},
|
|
196
|
+
"args": {
|
|
197
|
+
"order_id": { "type": "string", "required": true, "max_length": 128 }
|
|
198
|
+
},
|
|
199
|
+
"lookup": { "id_from_arg": "order_id" },
|
|
200
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "status_change_reason", "updated_at"],
|
|
201
|
+
"evidence": "required",
|
|
202
|
+
"max_rows": 1
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "orders.propose_status_change",
|
|
206
|
+
"kind": "proposal",
|
|
207
|
+
"source": "app_postgres",
|
|
208
|
+
"context": "local_operator",
|
|
209
|
+
"target": {
|
|
210
|
+
"schema": "public",
|
|
211
|
+
"table": "orders",
|
|
212
|
+
"primary_key": "id",
|
|
213
|
+
"tenant_key": "tenant_id"
|
|
214
|
+
},
|
|
215
|
+
"args": {
|
|
216
|
+
"order_id": { "type": "string", "required": true, "max_length": 128 },
|
|
217
|
+
"status": { "type": "string", "required": true, "enum": ["ready_to_ship", "canceled"] },
|
|
218
|
+
"reason": { "type": "string", "required": true, "max_length": 500 }
|
|
219
|
+
},
|
|
220
|
+
"lookup": { "id_from_arg": "order_id" },
|
|
221
|
+
"visible_columns": ["id", "tenant_id", "customer_id", "status", "status_change_reason", "updated_at"],
|
|
222
|
+
"evidence": "required",
|
|
223
|
+
"max_rows": 1,
|
|
224
|
+
"patch": {
|
|
225
|
+
"status": { "from_arg": "status" },
|
|
226
|
+
"status_change_reason": { "from_arg": "reason" }
|
|
227
|
+
},
|
|
228
|
+
"allowed_columns": ["status", "status_change_reason"],
|
|
229
|
+
"transition_guards": {
|
|
230
|
+
"status": {
|
|
231
|
+
"allowed": {
|
|
232
|
+
"paid": ["ready_to_ship", "canceled"],
|
|
233
|
+
"processing": ["ready_to_ship"]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"conflict_guard": { "column": "updated_at" },
|
|
238
|
+
"approval": { "mode": "human", "required_role": "orders_lead" }
|
|
134
239
|
}
|
|
135
240
|
]
|
|
136
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapsor/runner",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"description": "Commit-safe MCP runner for Postgres and MySQL agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"dist/runner.mjs",
|
|
18
18
|
"docs/**/*.md",
|
|
19
19
|
"examples/dangerous-mcp-tools.json",
|
|
20
|
+
"examples/openai-agents-http/**",
|
|
21
|
+
"examples/openai-agents-stdio/**",
|
|
20
22
|
"examples/reference-support-billing-app/**",
|
|
21
23
|
"recipes/**/*.json",
|
|
22
24
|
"README.md",
|