@synapsor/runner 0.1.0-alpha.1 → 0.1.0-alpha.10

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.
Files changed (44) hide show
  1. package/README.md +387 -19
  2. package/TRADEMARKS.md +23 -0
  3. package/dist/cli.d.ts +4 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +20 -8723
  6. package/dist/runner.mjs +12759 -0
  7. package/docs/README.md +36 -0
  8. package/docs/getting-started-own-database.md +460 -0
  9. package/docs/http-mcp.md +242 -0
  10. package/docs/limitations.md +95 -0
  11. package/docs/local-mode.md +351 -0
  12. package/docs/mcp-audit.md +152 -0
  13. package/docs/mcp-client-setup.md +231 -0
  14. package/docs/recipes.md +61 -0
  15. package/docs/release-notes.md +129 -0
  16. package/docs/security-boundary.md +94 -0
  17. package/docs/troubleshooting-first-run.md +248 -0
  18. package/docs/writeback-executors.md +209 -0
  19. package/examples/app-owned-writeback/README.md +120 -0
  20. package/examples/app-owned-writeback/business-actions.md +221 -0
  21. package/examples/app-owned-writeback/command-handler.mjs +46 -0
  22. package/examples/app-owned-writeback/node-fastify-handler.mjs +55 -0
  23. package/examples/app-owned-writeback/python-fastapi-handler.py +57 -0
  24. package/examples/dangerous-mcp-tools.json +88 -0
  25. package/examples/openai-agents-http/README.md +56 -0
  26. package/examples/openai-agents-http/agent.py +54 -0
  27. package/examples/openai-agents-http/requirements.txt +1 -0
  28. package/examples/openai-agents-stdio/README.md +62 -0
  29. package/examples/openai-agents-stdio/agent.py +70 -0
  30. package/examples/openai-agents-stdio/requirements.txt +1 -0
  31. package/examples/reference-support-billing-app/README.md +137 -0
  32. package/examples/reference-support-billing-app/docker-compose.yml +13 -0
  33. package/examples/reference-support-billing-app/mcp-client.generic.json +11 -0
  34. package/examples/reference-support-billing-app/schema.sql +68 -0
  35. package/examples/reference-support-billing-app/scripts/run-demo.sh +7 -0
  36. package/examples/reference-support-billing-app/seed.sql +33 -0
  37. package/examples/reference-support-billing-app/synapsor.runner.json +241 -0
  38. package/package.json +12 -4
  39. package/recipes/accounts.trial_extension.json +42 -0
  40. package/recipes/billing.late_fee_waiver.json +46 -0
  41. package/recipes/credits.account_credit.json +45 -0
  42. package/recipes/orders.refund_review.json +57 -0
  43. package/recipes/support.ticket_resolution.json +51 -0
  44. package/dist/bin.cjs +0 -13
@@ -0,0 +1,221 @@
1
+ # App-Owned Business Action Examples
2
+
3
+ These examples show the kinds of approved proposals an app-owned handler can
4
+ apply. They are intentionally not direct SQL examples. Your application service
5
+ owns the write transaction, re-checks authorization and row/version guards, and
6
+ returns a terminal receipt to Synapsor Runner.
7
+
8
+ Each request uses the same boundary:
9
+
10
+ ```text
11
+ model-facing MCP tool -> proposal
12
+ human/operator approval -> app-owned handler
13
+ handler transaction -> applied/conflict/failed receipt
14
+ local replay
15
+ ```
16
+
17
+ The handler must not trust request fields blindly. Re-check tenant,
18
+ principal/role, idempotency, row versions, and business policy before mutating
19
+ state.
20
+
21
+ ## Create A Refund Review
22
+
23
+ Use this when an agent may request a refund review, but your application must
24
+ create the review record through normal business logic.
25
+
26
+ Capability:
27
+
28
+ ```text
29
+ refunds.propose_refund_review(order_id, reason, requested_amount_cents)
30
+ ```
31
+
32
+ Handler request:
33
+
34
+ ```json
35
+ {
36
+ "schema_version": "synapsor.handler-writeback.v1",
37
+ "writeback_job_id": "hwb_wrp_refund_001",
38
+ "proposal_id": "wrp_refund_001",
39
+ "idempotency_key": "wrp_refund_001",
40
+ "change_set": {
41
+ "action": "refunds.propose_refund_review",
42
+ "scope": {
43
+ "tenant_id": "acme",
44
+ "principal": "support_lead@example.com",
45
+ "object_type": "order",
46
+ "object_id": "ORD-3001"
47
+ },
48
+ "before": {
49
+ "order_status": "delivered",
50
+ "refund_review_id": null
51
+ },
52
+ "patch": {
53
+ "requested_amount_cents": 2500,
54
+ "reason": "duplicate charge"
55
+ },
56
+ "after": {
57
+ "refund_review_status": "pending_review"
58
+ },
59
+ "guards": {
60
+ "expected_version": {
61
+ "column": "updated_at",
62
+ "value": "2026-06-20T14:31:08Z"
63
+ }
64
+ },
65
+ "evidence": {
66
+ "bundle_id": "ev_refund_001"
67
+ }
68
+ },
69
+ "executor": "app_writeback_api",
70
+ "dry_run": false
71
+ }
72
+ ```
73
+
74
+ Handler transaction sketch:
75
+
76
+ ```text
77
+ BEGIN
78
+ verify principal can request refunds for tenant acme
79
+ verify order ORD-3001 still belongs to tenant acme
80
+ verify order.updated_at still matches expected_version
81
+ verify no receipt exists for idempotency_key
82
+ INSERT INTO refund_reviews (...)
83
+ INSERT INTO synapsor_app_receipts (...)
84
+ COMMIT
85
+ ```
86
+
87
+ Receipt:
88
+
89
+ ```json
90
+ {
91
+ "status": "applied",
92
+ "rows_affected": 1,
93
+ "new_object_id": "RR-9001",
94
+ "source_database_mutated": true
95
+ }
96
+ ```
97
+
98
+ ## Insert An Account Credit Row
99
+
100
+ Use this when the safe write is an append-only ledger/accounting operation.
101
+
102
+ Capability:
103
+
104
+ ```text
105
+ credits.propose_account_credit(customer_id, amount_cents, reason)
106
+ ```
107
+
108
+ Handler transaction sketch:
109
+
110
+ ```text
111
+ BEGIN
112
+ verify customer belongs to trusted tenant
113
+ verify amount is within app policy
114
+ verify idempotency_key has not already created a credit
115
+ INSERT INTO account_credits (...)
116
+ UPDATE customers SET credit_balance_cents = credit_balance_cents + amount
117
+ INSERT INTO synapsor_app_receipts (...)
118
+ COMMIT
119
+ ```
120
+
121
+ Conflict receipt if the app policy no longer allows the credit:
122
+
123
+ ```json
124
+ {
125
+ "status": "conflict",
126
+ "safe_error_code": "CREDIT_POLICY_CHANGED",
127
+ "source_database_mutated": false,
128
+ "details": {
129
+ "reason": "customer credit limit changed after proposal"
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Open A Support Ticket
135
+
136
+ Use this when the agent may propose opening a ticket, but ticket creation must
137
+ go through your helpdesk/application service.
138
+
139
+ Capability:
140
+
141
+ ```text
142
+ support.propose_open_ticket(customer_id, subject, body)
143
+ ```
144
+
145
+ Handler transaction sketch:
146
+
147
+ ```text
148
+ BEGIN
149
+ verify principal can open support tickets for tenant
150
+ verify customer belongs to tenant
151
+ validate subject/body against app policy
152
+ INSERT INTO support_tickets (...)
153
+ INSERT INTO ticket_events (...)
154
+ INSERT INTO synapsor_app_receipts (...)
155
+ COMMIT
156
+ ```
157
+
158
+ Receipt:
159
+
160
+ ```json
161
+ {
162
+ "status": "applied",
163
+ "rows_affected": 2,
164
+ "new_object_id": "T-9100",
165
+ "source_database_mutated": true
166
+ }
167
+ ```
168
+
169
+ ## Update Multiple Related Rows
170
+
171
+ Use this when an approved business action spans several tables and should stay
172
+ inside your normal application transaction.
173
+
174
+ Capability:
175
+
176
+ ```text
177
+ subscriptions.propose_trial_extension(customer_id, extension_days, reason)
178
+ ```
179
+
180
+ Handler transaction sketch:
181
+
182
+ ```text
183
+ BEGIN
184
+ verify tenant/principal authorization
185
+ SELECT subscription FOR UPDATE
186
+ verify expected subscription version
187
+ UPDATE subscriptions SET trial_ends_at = ...
188
+ INSERT INTO customer_events (...)
189
+ INSERT INTO billing_notes (...)
190
+ INSERT INTO synapsor_app_receipts (...)
191
+ COMMIT
192
+ ```
193
+
194
+ Receipt:
195
+
196
+ ```json
197
+ {
198
+ "status": "applied",
199
+ "rows_affected": 3,
200
+ "previous_version": "2026-06-20T14:31:08Z",
201
+ "new_version": "2026-06-20T14:34:19Z",
202
+ "source_database_mutated": true
203
+ }
204
+ ```
205
+
206
+ ## Idempotent Retry
207
+
208
+ If the same `idempotency_key` reaches your handler again after a successful
209
+ apply, return `already_applied` and the original receipt details rather than
210
+ running the transaction again.
211
+
212
+ ```json
213
+ {
214
+ "status": "already_applied",
215
+ "rows_affected": 0,
216
+ "source_database_mutated": false,
217
+ "details": {
218
+ "original_receipt_id": "rct_abc123"
219
+ }
220
+ }
221
+ ```
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ const chunks = [];
4
+ for await (const chunk of process.stdin) chunks.push(chunk);
5
+
6
+ const request = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
7
+ const changeSet = request.change_set || {};
8
+
9
+ if (!request.proposal_id || !request.idempotency_key || !changeSet.scope?.tenant_id) {
10
+ process.stdout.write(JSON.stringify({
11
+ status: "failed",
12
+ safe_error_code: "BAD_WRITEBACK_REQUEST",
13
+ source_database_mutated: false,
14
+ }));
15
+ process.exit(0);
16
+ }
17
+
18
+ if (request.dry_run) {
19
+ process.stdout.write(JSON.stringify({
20
+ status: "applied",
21
+ rows_affected: 0,
22
+ source_database_mutated: false,
23
+ details: { dry_run: true },
24
+ }));
25
+ process.exit(0);
26
+ }
27
+
28
+ /*
29
+ * Put your app-owned command transaction here.
30
+ *
31
+ * Examples:
32
+ * - call an internal service;
33
+ * - enqueue a review job;
34
+ * - run an app migration-safe script that uses your normal ORM.
35
+ *
36
+ * Re-check tenant/principal authorization, idempotency, row/version guards,
37
+ * and business policy before mutating application state.
38
+ */
39
+
40
+ process.stdout.write(JSON.stringify({
41
+ status: "applied",
42
+ rows_affected: 1,
43
+ previous_version: String(changeSet.guards?.expected_version?.value || ""),
44
+ new_version: new Date().toISOString(),
45
+ source_database_mutated: true,
46
+ }));
@@ -0,0 +1,55 @@
1
+ import Fastify from "fastify";
2
+
3
+ const port = Number(process.env.PORT || 8787);
4
+ const expectedToken = process.env.SYNAPSOR_APP_WRITEBACK_TOKEN || "dev-handler-token";
5
+
6
+ const app = Fastify({ logger: true });
7
+
8
+ app.post("/synapsor/writeback", async (request, reply) => {
9
+ const auth = request.headers.authorization || "";
10
+ if (auth !== `Bearer ${expectedToken}`) {
11
+ return reply.code(401).send({ status: "failed", error_code: "UNAUTHORIZED" });
12
+ }
13
+
14
+ const body = request.body || {};
15
+ const changeSet = body.change_set || {};
16
+
17
+ if (!body.proposal_id || !body.idempotency_key || !changeSet.scope?.tenant_id) {
18
+ return reply.code(400).send({ status: "failed", error_code: "BAD_WRITEBACK_REQUEST" });
19
+ }
20
+
21
+ if (body.dry_run) {
22
+ return {
23
+ status: "applied",
24
+ rows_affected: 0,
25
+ source_database_mutated: false,
26
+ details: { dry_run: true },
27
+ };
28
+ }
29
+
30
+ /*
31
+ * Put your app-owned transaction here.
32
+ *
33
+ * Examples:
34
+ * - insert a refund_review row;
35
+ * - insert an account_credit row;
36
+ * - open a support_ticket row;
37
+ * - update invoice + ledger rows together.
38
+ *
39
+ * Re-check:
40
+ * - tenant and principal authorization;
41
+ * - idempotency_key has not already been applied;
42
+ * - row/version guards still match;
43
+ * - requested business action is allowed by your app policy.
44
+ */
45
+
46
+ return {
47
+ status: "applied",
48
+ rows_affected: 1,
49
+ previous_version: String(changeSet.guards?.expected_version?.value || ""),
50
+ new_version: new Date().toISOString(),
51
+ source_database_mutated: true,
52
+ };
53
+ });
54
+
55
+ app.listen({ host: "127.0.0.1", port });
@@ -0,0 +1,57 @@
1
+ import os
2
+ from datetime import datetime, timezone
3
+
4
+ from fastapi import FastAPI, Header, HTTPException
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class HandlerRequest(BaseModel):
9
+ schema_version: str
10
+ writeback_job_id: str
11
+ proposal_id: str
12
+ idempotency_key: str
13
+ change_set: dict
14
+ executor: str | None = None
15
+ dry_run: bool = False
16
+
17
+
18
+ app = FastAPI()
19
+ EXPECTED_TOKEN = os.environ.get("SYNAPSOR_APP_WRITEBACK_TOKEN", "dev-handler-token")
20
+
21
+
22
+ @app.post("/synapsor/writeback")
23
+ def writeback(request: HandlerRequest, authorization: str | None = Header(default=None)):
24
+ if authorization != f"Bearer {EXPECTED_TOKEN}":
25
+ raise HTTPException(status_code=401, detail="UNAUTHORIZED")
26
+
27
+ scope = request.change_set.get("scope", {})
28
+ if not scope.get("tenant_id"):
29
+ raise HTTPException(status_code=400, detail="BAD_WRITEBACK_REQUEST")
30
+
31
+ if request.dry_run:
32
+ return {
33
+ "status": "applied",
34
+ "rows_affected": 0,
35
+ "source_database_mutated": False,
36
+ "details": {"dry_run": True},
37
+ }
38
+
39
+ # Put your app-owned transaction here.
40
+ #
41
+ # Examples:
42
+ # - insert a refund_review row;
43
+ # - insert an account_credit row;
44
+ # - open a support_ticket row;
45
+ # - update invoice + ledger rows together.
46
+ #
47
+ # Re-check tenant/principal authorization, idempotency, row/version guards,
48
+ # and business policy before mutating application state.
49
+
50
+ expected = request.change_set.get("guards", {}).get("expected_version", {})
51
+ return {
52
+ "status": "applied",
53
+ "rows_affected": 1,
54
+ "previous_version": str(expected.get("value", "")),
55
+ "new_version": datetime.now(timezone.utc).isoformat(),
56
+ "source_database_mutated": True,
57
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "tools": [
3
+ {
4
+ "name": "execute_sql",
5
+ "description": "Execute arbitrary SQL against the application database.",
6
+ "inputSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "sql": { "type": "string" }
10
+ },
11
+ "required": ["sql"]
12
+ }
13
+ },
14
+ {
15
+ "name": "run_query",
16
+ "description": "Run any query and return database rows.",
17
+ "inputSchema": {
18
+ "type": "object",
19
+ "properties": {
20
+ "query": { "type": "string" },
21
+ "table": { "type": "string" },
22
+ "columns": {
23
+ "type": "array",
24
+ "items": { "type": "string" }
25
+ }
26
+ },
27
+ "required": ["query"]
28
+ }
29
+ },
30
+ {
31
+ "name": "approve_refund",
32
+ "description": "Approve and issue a customer refund immediately.",
33
+ "inputSchema": {
34
+ "type": "object",
35
+ "properties": {
36
+ "refund_id": { "type": "string" },
37
+ "tenant_id": { "type": "string" },
38
+ "amount_cents": { "type": "number" }
39
+ },
40
+ "required": ["refund_id", "tenant_id", "amount_cents"]
41
+ }
42
+ },
43
+ {
44
+ "name": "update_customer",
45
+ "description": "Update a customer record directly.",
46
+ "inputSchema": {
47
+ "type": "object",
48
+ "properties": {
49
+ "customer_id": { "type": "string" },
50
+ "tenant_id": { "type": "string" },
51
+ "column": { "type": "string" },
52
+ "value": { "type": "string" }
53
+ },
54
+ "required": ["customer_id", "tenant_id", "column", "value"]
55
+ }
56
+ },
57
+ {
58
+ "name": "delete_order",
59
+ "description": "Delete an order from the database.",
60
+ "inputSchema": {
61
+ "type": "object",
62
+ "properties": {
63
+ "order_id": { "type": "string" },
64
+ "tenant_id": { "type": "string" }
65
+ },
66
+ "required": ["order_id", "tenant_id"]
67
+ }
68
+ },
69
+ {
70
+ "name": "query_database",
71
+ "description": "Query arbitrary tables and columns from the database.",
72
+ "inputSchema": {
73
+ "type": "object",
74
+ "properties": {
75
+ "database": { "type": "string" },
76
+ "schema": { "type": "string" },
77
+ "table": { "type": "string" },
78
+ "columns": {
79
+ "type": "array",
80
+ "items": { "type": "string" }
81
+ },
82
+ "where": { "type": "string" }
83
+ },
84
+ "required": ["table"]
85
+ }
86
+ }
87
+ ]
88
+ }
@@ -0,0 +1,56 @@
1
+ # OpenAI Agents SDK + Synapsor Runner Streamable HTTP MCP
2
+
3
+ This example shows an OpenAI Agents SDK app connecting to a long-running
4
+ Synapsor Runner Streamable HTTP MCP server.
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
+ This example uses `synapsor-runner mcp serve-streamable-http`, the
11
+ spec-compatible HTTP MCP transport with `initialize` and session behavior. Use
12
+ `synapsor-runner mcp serve-http` only when you intentionally want the smaller
13
+ JSON-RPC bridge and an app-owned wrapper.
14
+
15
+ The model still sees a semantic action. It does not receive raw SQL, database
16
+ URLs, write credentials, approval tools, or commit tools.
17
+
18
+ ## Terminal 1: Start Synapsor Runner HTTP MCP
19
+
20
+ ```bash
21
+ export DATABASE_URL="<postgres-or-mysql-read-url>"
22
+ export SYNAPSOR_TENANT_ID="acme"
23
+ export SYNAPSOR_PRINCIPAL="openai_agent_demo"
24
+ export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
25
+
26
+ npx -y -p @synapsor/runner@alpha synapsor-runner mcp serve-streamable-http \
27
+ --config ./synapsor.runner.json \
28
+ --store ./.synapsor/local.db \
29
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
30
+ ```
31
+
32
+ ## Terminal 2: Run The Agent
33
+
34
+ ```bash
35
+ python -m venv .venv
36
+ . .venv/bin/activate
37
+ pip install -r requirements.txt
38
+
39
+ export OPENAI_API_KEY="..."
40
+ export SYNAPSOR_RUNNER_HTTP_URL="http://127.0.0.1:8766/mcp"
41
+ export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
42
+ export SYNAPSOR_INVOICE_ID="INV-3001"
43
+
44
+ python agent.py
45
+ ```
46
+
47
+ Expected behavior:
48
+
49
+ - the agent calls `billing.inspect_invoice` through Synapsor HTTP MCP;
50
+ - Synapsor applies trusted tenant/principal context from the server process;
51
+ - the response includes scoped data and evidence handles;
52
+ - no SQL/write/approval tool is exposed to the model;
53
+ - evidence/query audit are saved in the local Runner store.
54
+
55
+ For production-like deployment, keep HTTP MCP behind private networking/TLS,
56
+ bearer auth, and rate limits. See [HTTP MCP](../../docs/http-mcp.md).
@@ -0,0 +1,54 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+
5
+ try:
6
+ from agents import Agent, Runner
7
+ from agents.mcp import MCPServerStreamableHttp
8
+ except ImportError as exc:
9
+ raise SystemExit(
10
+ "This example requires the OpenAI Agents SDK with Streamable HTTP MCP support. "
11
+ "Install with: pip install -r requirements.txt"
12
+ ) from exc
13
+
14
+
15
+ async def main() -> None:
16
+ required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "SYNAPSOR_RUNNER_HTTP_TOKEN"]
17
+ missing = [name for name in required if not os.environ.get(name)]
18
+ if missing:
19
+ raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
20
+
21
+ invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
22
+ mcp_url = os.environ["SYNAPSOR_RUNNER_HTTP_URL"]
23
+ token = os.environ["SYNAPSOR_RUNNER_HTTP_TOKEN"]
24
+
25
+ async with MCPServerStreamableHttp(
26
+ params={
27
+ "url": mcp_url,
28
+ "headers": {"Authorization": f"Bearer {token}"},
29
+ "timeout": 15,
30
+ }
31
+ ) as mcp_server:
32
+ agent = Agent(
33
+ name="Synapsor Streamable HTTP MCP demo agent",
34
+ instructions=(
35
+ "Use Synapsor MCP tools to inspect scoped database data. "
36
+ "Do not claim that you can run SQL, approve proposals, or commit writes."
37
+ ),
38
+ mcp_servers=[mcp_server],
39
+ )
40
+ result = await Runner.run(
41
+ agent,
42
+ (
43
+ f"Inspect invoice {invoice_id} using Synapsor. "
44
+ "Explain what you saw and whether you have write authority."
45
+ ),
46
+ )
47
+ print(result.final_output)
48
+
49
+
50
+ if __name__ == "__main__":
51
+ try:
52
+ asyncio.run(main())
53
+ except KeyboardInterrupt:
54
+ 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-runner demo
17
+ ```
18
+
19
+ or connect your own staging database:
20
+
21
+ ```bash
22
+ npx -y -p @synapsor/runner@alpha synapsor-runner onboard db --from-env DATABASE_URL
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.