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

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 (34) hide show
  1. package/README.md +41 -2
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/runner.mjs +264 -65
  4. package/docs/README.md +17 -0
  5. package/docs/app-owned-executors.md +21 -0
  6. package/docs/cloud-mode.md +24 -0
  7. package/docs/current-scope.md +24 -0
  8. package/docs/dependency-license-inventory.md +35 -0
  9. package/docs/http-mcp.md +35 -1
  10. package/docs/licensing.md +36 -0
  11. package/docs/mcp-client-setup.md +39 -0
  12. package/docs/openai-agents-sdk.md +57 -0
  13. package/docs/release-notes.md +30 -1
  14. package/docs/use-your-own-database.md +18 -0
  15. package/docs/writeback-executors.md +11 -0
  16. package/examples/mcp-postgres-billing-app-handler/README.md +82 -0
  17. package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +197 -0
  18. package/examples/mcp-postgres-billing-app-handler/docker-compose.yml +13 -0
  19. package/examples/mcp-postgres-billing-app-handler/schema.sql +59 -0
  20. package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +99 -0
  21. package/examples/mcp-postgres-billing-app-handler/seed.sql +39 -0
  22. package/examples/mcp-postgres-billing-app-handler/synapsor.runner.json +157 -0
  23. package/examples/openai-agents-http/README.md +10 -2
  24. package/examples/openai-agents-stdio/README.md +8 -4
  25. package/examples/openai-agents-stdio/agent.py +2 -0
  26. package/fixtures/benchmark/mcp-efficiency.json +53 -0
  27. package/fixtures/benchmark/mcp-efficiency.txt +25 -0
  28. package/fixtures/protocol/MANIFEST.json +54 -0
  29. package/fixtures/protocol/change-set.late-fee-waiver.v1.json +72 -0
  30. package/fixtures/protocol/execution-receipt.applied.v1.json +14 -0
  31. package/fixtures/protocol/execution-receipt.conflict.v1.json +15 -0
  32. package/fixtures/protocol/runner-registration.v1.json +22 -0
  33. package/fixtures/protocol/writeback-job.late-fee-waiver.v1.json +44 -0
  34. package/package.json +3 -1
package/README.md CHANGED
@@ -20,6 +20,10 @@ These are current alpha requirements, not hidden behavior:
20
20
  - `synapsor-runner mcp serve-streamable-http` is standard MCP Streamable HTTP
21
21
  with `initialize` and in-memory session behavior for SDK/client HTTP MCP
22
22
  integrations.
23
+ - OpenAI Agents SDK rejects dotted function/tool names. Use
24
+ `--alias-mode openai` or `--openai-tool-aliases` for OpenAI-facing MCP
25
+ transports. Runner exposes aliases such as `billing__inspect_invoice` and
26
+ keeps the canonical Synapsor capability name in tool metadata.
23
27
  - `synapsor-runner mcp serve-http` is a small authenticated JSON-RPC bridge for
24
28
  `tools/list`, `tools/call`, and `resources/read`. Use it only when you want a
25
29
  simple app/server wrapper instead of full HTTP MCP.
@@ -199,6 +203,15 @@ synapsor-runner mcp serve \
199
203
  --store ./.synapsor/local.db
200
204
  ```
201
205
 
206
+ For OpenAI Agents SDK over stdio, add OpenAI-safe aliases:
207
+
208
+ ```bash
209
+ synapsor-runner mcp serve \
210
+ --config ./synapsor.runner.json \
211
+ --store ./.synapsor/local.db \
212
+ --alias-mode openai
213
+ ```
214
+
202
215
  App/server deployments:
203
216
 
204
217
  ```bash
@@ -207,12 +220,29 @@ export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
207
220
  synapsor-runner mcp serve-streamable-http \
208
221
  --config ./synapsor.runner.json \
209
222
  --store ./.synapsor/local.db \
210
- --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
223
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN \
224
+ --alias-mode openai
225
+ ```
226
+
227
+ Equivalent unified command:
228
+
229
+ ```bash
230
+ synapsor-runner mcp serve \
231
+ --transport streamable-http \
232
+ --config ./synapsor.runner.json \
233
+ --store ./.synapsor/local.db \
234
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN \
235
+ --alias-mode openai
211
236
  ```
212
237
 
213
238
  Streamable HTTP defaults to `127.0.0.1:8766`, requires bearer auth by default,
214
239
  and should use private networking, TLS, and rate limits before being exposed
215
- beyond a local machine.
240
+ beyond a local machine. With `--alias-mode openai`, tools are exposed to
241
+ the model as OpenAI-safe aliases such as `billing__inspect_invoice`; `_meta`
242
+ still includes `synapsor.canonical_tool_name = billing.inspect_invoice`, and
243
+ Runner routes calls back to the canonical Synapsor capability. Use
244
+ `--alias-mode both` during migrations if one client still expects canonical
245
+ dotted names while another needs OpenAI-safe aliases.
216
246
 
217
247
  Bridge mode:
218
248
 
@@ -245,6 +275,15 @@ Before asking an agent to solve a real task, confirm it can call a Runner tool:
245
275
  synapsor-runner tools preview --config ./synapsor.runner.json --store ./.synapsor/local.db
246
276
  ```
247
277
 
278
+ For OpenAI-facing clients:
279
+
280
+ ```bash
281
+ synapsor-runner tools preview \
282
+ --config ./synapsor.runner.json \
283
+ --store ./.synapsor/local.db \
284
+ --alias-mode openai
285
+ ```
286
+
248
287
  Then ask the agent:
249
288
 
250
289
  ```text
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAYA,OAAO,EAAkF,KAAK,WAAW,EAAoD,MAAM,6BAA6B,CAAC;AAkBjM,OAAO,EAA6G,KAAK,YAAY,EAAwB,MAAM,2BAA2B,CAAC;AAC/L,OAAO,EAOL,KAAK,gBAAgB,EAEtB,MAAM,mCAAmC,CAAC;AAkS3C,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1D;AA0DD,KAAK,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9E,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACvC,GACL,OAAO,CAAC,MAAM,CAAC,CA2OjB;AAo9CD,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/H"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAYA,OAAO,EAAqG,KAAK,WAAW,EAAwE,MAAM,6BAA6B,CAAC;AAkBxO,OAAO,EAA6G,KAAK,YAAY,EAAwB,MAAM,2BAA2B,CAAC;AAC/L,OAAO,EAOL,KAAK,gBAAgB,EAEtB,MAAM,mCAAmC,CAAC;AAkS3C,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1D;AA0DD,KAAK,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9E,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;CACvC,GACL,OAAO,CAAC,MAAM,CAAC,CA2OjB;AAo9CD,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/H"}
package/dist/runner.mjs CHANGED
@@ -2685,59 +2685,74 @@ function createMcpRuntime(config, options = {}) {
2685
2685
  }
2686
2686
  };
2687
2687
  }
2688
- function createSynapsorMcpServer(runtime) {
2688
+ function createSynapsorMcpServer(runtime, options = {}) {
2689
2689
  const server = new McpServer(
2690
- { name: "synapsor-runner", version: "0.1.0-alpha.10" },
2690
+ { name: "synapsor-runner", version: "0.1.0-alpha.11" },
2691
2691
  { capabilities: { tools: {}, resources: {} } }
2692
2692
  );
2693
+ const toolNameStyle = options.toolNameStyle ?? "canonical";
2693
2694
  if (runtime.config.mode === "cloud") {
2694
- for (const tool of runtime.listTools()) {
2695
- server.registerTool(
2696
- tool.name,
2697
- {
2698
- title: tool.title,
2699
- description: tool.description,
2700
- inputSchema: zodInputShapeFromJsonSchema(tool.input_schema),
2701
- annotations: {
2702
- readOnlyHint: Boolean(tool.annotations.readOnlyHint),
2703
- destructiveHint: false,
2704
- idempotentHint: Boolean(tool.annotations.idempotentHint),
2705
- openWorldHint: false
2695
+ const tools2 = runtime.listTools();
2696
+ const exposedNames = toolNameExposureMap(tools2.map((tool) => tool.name), toolNameStyle);
2697
+ for (const tool of tools2) {
2698
+ for (const exposedName of exposedNames.get(tool.name) ?? [tool.name]) {
2699
+ server.registerTool(
2700
+ exposedName,
2701
+ {
2702
+ title: tool.title,
2703
+ description: tool.description,
2704
+ inputSchema: zodInputShapeFromJsonSchema(tool.input_schema),
2705
+ annotations: {
2706
+ readOnlyHint: Boolean(tool.annotations.readOnlyHint),
2707
+ destructiveHint: false,
2708
+ idempotentHint: Boolean(tool.annotations.idempotentHint),
2709
+ openWorldHint: false
2710
+ },
2711
+ _meta: {
2712
+ ...tool.annotations,
2713
+ "synapsor.cloud_delegated": true,
2714
+ "synapsor.canonical_tool_name": tool.name,
2715
+ "synapsor.exposed_tool_name": exposedName,
2716
+ "synapsor.tool_name_style": toolNameStyle,
2717
+ "synapsor.raw_sql_exposed": false,
2718
+ "synapsor.approval_tool": false
2719
+ }
2706
2720
  },
2707
- _meta: {
2708
- ...tool.annotations,
2709
- "synapsor.cloud_delegated": true,
2710
- "synapsor.raw_sql_exposed": false,
2711
- "synapsor.approval_tool": false
2712
- }
2713
- },
2714
- async (args) => toolCallResult(runtime, tool.name, args)
2715
- );
2721
+ async (args) => toolCallResult(runtime, tool.name, args)
2722
+ );
2723
+ }
2716
2724
  }
2717
2725
  } else {
2718
- for (const capability of listedLocalCapabilities(runtime.config)) {
2719
- server.registerTool(
2720
- capability.name,
2721
- {
2722
- title: capability.name,
2723
- description: capabilityDescription(capability),
2724
- inputSchema: zodInputShape(capability),
2725
- annotations: {
2726
- readOnlyHint: capability.kind === "read",
2727
- destructiveHint: false,
2728
- idempotentHint: capability.kind === "read",
2729
- openWorldHint: false
2726
+ const capabilities = listedLocalCapabilities(runtime.config);
2727
+ const exposedNames = toolNameExposureMap(capabilities.map((capability) => capability.name), toolNameStyle);
2728
+ for (const capability of capabilities) {
2729
+ for (const exposedName of exposedNames.get(capability.name) ?? [capability.name]) {
2730
+ server.registerTool(
2731
+ exposedName,
2732
+ {
2733
+ title: capability.name,
2734
+ description: capabilityDescription(capability),
2735
+ inputSchema: zodInputShape(capability),
2736
+ annotations: {
2737
+ readOnlyHint: capability.kind === "read",
2738
+ destructiveHint: false,
2739
+ idempotentHint: capability.kind === "read",
2740
+ openWorldHint: false
2741
+ },
2742
+ _meta: {
2743
+ "synapsor.kind": capability.kind,
2744
+ "synapsor.source": capability.source,
2745
+ "synapsor.target": `${capability.target.schema}.${capability.target.table}`,
2746
+ "synapsor.canonical_tool_name": capability.name,
2747
+ "synapsor.exposed_tool_name": exposedName,
2748
+ "synapsor.tool_name_style": toolNameStyle,
2749
+ "synapsor.raw_sql_exposed": false,
2750
+ "synapsor.approval_tool": false
2751
+ }
2730
2752
  },
2731
- _meta: {
2732
- "synapsor.kind": capability.kind,
2733
- "synapsor.source": capability.source,
2734
- "synapsor.target": `${capability.target.schema}.${capability.target.table}`,
2735
- "synapsor.raw_sql_exposed": false,
2736
- "synapsor.approval_tool": false
2737
- }
2738
- },
2739
- async (args) => toolCallResult(runtime, capability.name, args)
2740
- );
2753
+ async (args) => toolCallResult(runtime, capability.name, args)
2754
+ );
2755
+ }
2741
2756
  }
2742
2757
  }
2743
2758
  server.registerResource(
@@ -2764,7 +2779,7 @@ async function serveStdio(options = {}) {
2764
2779
  const config = options.config ?? loadRuntimeConfigFromFile(options.configPath);
2765
2780
  const cloudTools = config.mode === "cloud" ? await fetchCloudToolMetadata(config, process.env) : void 0;
2766
2781
  const runtime = createMcpRuntime(config, { storePath: options.storePath, cloudTools });
2767
- const server = createSynapsorMcpServer(runtime);
2782
+ const server = createSynapsorMcpServer(runtime, { toolNameStyle: options.toolNameStyle });
2768
2783
  const transport = new StdioServerTransport();
2769
2784
  await server.connect(transport);
2770
2785
  await new Promise((resolve) => {
@@ -2872,6 +2887,7 @@ async function startStreamableHttpMcpServer(options = {}) {
2872
2887
  readRow: options.readRow,
2873
2888
  cloudTools,
2874
2889
  env,
2890
+ toolNameStyle: options.toolNameStyle,
2875
2891
  authToken,
2876
2892
  devNoAuth,
2877
2893
  corsOrigin: options.corsOrigin,
@@ -2914,7 +2930,7 @@ async function startStreamableHttpMcpServer(options = {}) {
2914
2930
  };
2915
2931
  }
2916
2932
  async function handleStreamableHttpMcpRequest(input) {
2917
- const { request, response, config, storePath, readRow, cloudTools, env, authToken, devNoAuth, corsOrigin, sessions, openSessions } = input;
2933
+ const { request, response, config, storePath, readRow, cloudTools, env, toolNameStyle, authToken, devNoAuth, corsOrigin, sessions, openSessions } = input;
2918
2934
  try {
2919
2935
  setCorsHeaders(response, corsOrigin);
2920
2936
  if (request.method === "OPTIONS" && corsOrigin) {
@@ -2982,7 +2998,7 @@ async function handleStreamableHttpMcpRequest(input) {
2982
2998
  transport.onclose = () => {
2983
2999
  if (session) disposeStreamableSession(session, sessions, openSessions);
2984
3000
  };
2985
- await createSynapsorMcpServer(runtime).connect(transport);
3001
+ await createSynapsorMcpServer(runtime, { toolNameStyle }).connect(transport);
2986
3002
  await transport.handleRequest(request, response, parsedBody);
2987
3003
  } catch (error) {
2988
3004
  const message = sanitizeHttpError(error, authToken);
@@ -3103,6 +3119,50 @@ function requestIdFromPayload(payload) {
3103
3119
  }
3104
3120
  return isRecord3(payload) ? payload.id ?? null : null;
3105
3121
  }
3122
+ function openaiToolNameAlias(canonicalName) {
3123
+ const sanitized = canonicalName.replace(/[^A-Za-z0-9_-]+/g, "__").replace(/_{3,}/g, "__").replace(/^_+|_+$/g, "");
3124
+ const base = sanitized.length > 0 ? sanitized : `tool_${shortToolHash(canonicalName)}`;
3125
+ if (base.length <= 64) return base;
3126
+ const suffix = shortToolHash(canonicalName);
3127
+ return `${base.slice(0, Math.max(1, 63 - suffix.length)).replace(/_+$/g, "")}_${suffix}`;
3128
+ }
3129
+ function toolNameExposures(canonicalNames, style) {
3130
+ const exposedNames = toolNameExposureMap(canonicalNames, style);
3131
+ return canonicalNames.flatMap((canonicalName) => {
3132
+ return (exposedNames.get(canonicalName) ?? [canonicalName]).map((exposedName) => ({
3133
+ canonicalName,
3134
+ exposedName,
3135
+ isAlias: exposedName !== canonicalName,
3136
+ style
3137
+ }));
3138
+ });
3139
+ }
3140
+ function toolNameExposureMap(canonicalNames, style) {
3141
+ const exposedByCanonical = /* @__PURE__ */ new Map();
3142
+ const canonicalByExposed = /* @__PURE__ */ new Map();
3143
+ if (style === "both") {
3144
+ for (const canonical of canonicalNames) canonicalByExposed.set(canonical, canonical);
3145
+ }
3146
+ for (const canonical of canonicalNames) {
3147
+ const names = /* @__PURE__ */ new Set();
3148
+ if (style === "canonical" || style === "both") names.add(canonical);
3149
+ if (style === "openai" || style === "both") {
3150
+ let alias = openaiToolNameAlias(canonical);
3151
+ const existing = canonicalByExposed.get(alias);
3152
+ if (existing && existing !== canonical) {
3153
+ const suffix = shortToolHash(canonical);
3154
+ alias = `${alias.slice(0, Math.max(1, 63 - suffix.length)).replace(/_+$/g, "")}_${suffix}`;
3155
+ }
3156
+ canonicalByExposed.set(alias, canonical);
3157
+ names.add(alias);
3158
+ }
3159
+ exposedByCanonical.set(canonical, [...names]);
3160
+ }
3161
+ return exposedByCanonical;
3162
+ }
3163
+ function shortToolHash(value) {
3164
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, 8);
3165
+ }
3106
3166
  function setCorsHeaders(response, corsOrigin) {
3107
3167
  if (corsOrigin) {
3108
3168
  response.setHeader("access-control-allow-origin", corsOrigin);
@@ -8633,7 +8693,7 @@ async function cloudConnect(args) {
8633
8693
  return 1;
8634
8694
  }
8635
8695
  const runnerId = String(parsed.cloud.runner_id || process2.env.SYNAPSOR_RUNNER_ID || "synapsor_runner_local").trim();
8636
- const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.10").trim();
8696
+ const runnerVersion = String(parsed.cloud.runner_version || process2.env.npm_package_version || "0.1.0-alpha.11").trim();
8637
8697
  const engines = normalizeEngines(parsed.cloud.engines);
8638
8698
  const capabilities = normalizeCapabilities(parsed.cloud.capabilities);
8639
8699
  const client = new ControlPlaneClient({
@@ -8687,6 +8747,7 @@ async function mcp(args) {
8687
8747
  if (subcommand === "serve-streamable-http") return mcpServeStreamableHttp(rest);
8688
8748
  if (subcommand === "audit") return mcpAudit(rest);
8689
8749
  if (subcommand === "config") return mcpConfig(rest);
8750
+ if (subcommand === "client-config") return mcpConfigure(rest);
8690
8751
  if (subcommand === "configure") return mcpConfigure(rest);
8691
8752
  if (subcommand === "smoke") return mcpSmoke(rest);
8692
8753
  usage(["mcp"]);
@@ -9345,13 +9406,19 @@ function quickDemoChangeSet() {
9345
9406
  };
9346
9407
  }
9347
9408
  async function mcpServe(args) {
9409
+ const transport = optionalArg(args, "--transport") ?? "stdio";
9410
+ if (transport === "streamable-http") return mcpServeStreamableHttp(args);
9411
+ if (transport === "http" || transport === "json-rpc-http" || transport === "jsonrpc-http") return mcpServeHttp(args);
9412
+ if (transport !== "stdio") throw new Error("--transport must be stdio, streamable-http, or http");
9348
9413
  const configPath = optionalArg(args, "--config") ?? process2.env.SYNAPSOR_MCP_CONFIG;
9349
9414
  const readOnly = args.includes("--read-only");
9350
9415
  const config = readOnly ? { ...await readRuntimeConfig(configPath ?? defaultConfigPath), mode: "read_only" } : void 0;
9416
+ const toolNameStyle = toolNameStyleOption(args);
9351
9417
  await serveStdio({
9352
9418
  configPath,
9353
9419
  storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE,
9354
- config
9420
+ config,
9421
+ toolNameStyle
9355
9422
  });
9356
9423
  return 0;
9357
9424
  }
@@ -9394,6 +9461,7 @@ async function mcpServeStreamableHttp(args) {
9394
9461
  const configPath = optionalArg(args, "--config") ?? process2.env.SYNAPSOR_MCP_CONFIG;
9395
9462
  const readOnly = args.includes("--read-only");
9396
9463
  const config = readOnly ? { ...await readRuntimeConfig(configPath ?? defaultConfigPath), mode: "read_only" } : void 0;
9464
+ const toolNameStyle = toolNameStyleOption(args);
9397
9465
  const host = optionalArg(args, "--host") ?? "127.0.0.1";
9398
9466
  const port = Number(optionalArg(args, "--port") ?? "8766");
9399
9467
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
@@ -9408,6 +9476,7 @@ async function mcpServeStreamableHttp(args) {
9408
9476
  storePath: optionalArg(args, "--store") ?? process2.env.SYNAPSOR_LOCAL_STORE,
9409
9477
  host,
9410
9478
  port,
9479
+ toolNameStyle,
9411
9480
  authTokenEnv: optionalArg(args, "--auth-token-env") ?? "SYNAPSOR_RUNNER_HTTP_TOKEN",
9412
9481
  devNoAuth: args.includes("--dev-no-auth"),
9413
9482
  corsOrigin: optionalArg(args, "--cors-origin")
@@ -9425,6 +9494,21 @@ async function mcpServeStreamableHttp(args) {
9425
9494
  });
9426
9495
  return 0;
9427
9496
  }
9497
+ function toolNameStyleOption(args) {
9498
+ const requestedStyle = optionalArg(args, "--tool-name-style");
9499
+ const requestedAliasMode = optionalArg(args, "--alias-mode");
9500
+ if (requestedStyle && requestedAliasMode && requestedStyle !== requestedAliasMode) {
9501
+ throw new Error("--tool-name-style and --alias-mode must match when both are provided");
9502
+ }
9503
+ const requested = requestedAliasMode ?? requestedStyle;
9504
+ if (args.includes("--openai-tool-aliases")) {
9505
+ if (requested && requested !== "openai") throw new Error("--openai-tool-aliases cannot be combined with a non-openai alias mode");
9506
+ return "openai";
9507
+ }
9508
+ if (!requested) return "canonical";
9509
+ if (requested === "canonical" || requested === "openai" || requested === "both") return requested;
9510
+ throw new Error("--alias-mode must be canonical, openai, or both");
9511
+ }
9428
9512
  async function mcpAudit(args) {
9429
9513
  const format = optionalArg(args, "--format") ?? (args.includes("--json") ? "json" : "text");
9430
9514
  if (!["text", "json", "markdown"].includes(format)) {
@@ -9567,21 +9651,29 @@ function formatProposeResult(capabilityName, result, storePath) {
9567
9651
  `;
9568
9652
  }
9569
9653
  async function mcpConfigure(args) {
9570
- const client = optionalArg(args, "--client");
9571
- if (!client) throw new Error("mcp configure requires --client generic-stdio|claude-desktop|cursor|vscode");
9654
+ const client = normalizeMcpClientName(optionalArg(args, "--client"));
9655
+ if (!client) throw new Error("mcp configure requires --client generic-stdio|claude|claude-desktop|cursor|vscode|openai-agents");
9572
9656
  const useAbsolutePaths = args.includes("--absolute-paths");
9573
9657
  const rawConfigPath = optionalArg(args, "--config") ?? "./synapsor.runner.json";
9574
9658
  const rawStorePath = optionalArg(args, "--store") ?? "./.synapsor/local.db";
9575
9659
  const configPath = useAbsolutePaths ? path3.resolve(rawConfigPath) : rawConfigPath;
9576
9660
  const storePath = useAbsolutePaths ? path3.resolve(rawStorePath) : rawStorePath;
9661
+ const transport = mcpClientConfigTransport(args, client);
9662
+ const aliasMode = mcpClientConfigAliasMode(args, client);
9663
+ const host = optionalArg(args, "--host") ?? "127.0.0.1";
9664
+ const port = Number(optionalArg(args, "--port") ?? "8766");
9665
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
9666
+ throw new Error("--port must be an integer from 1 to 65535");
9667
+ }
9668
+ const authTokenEnv = optionalArg(args, "--auth-token-env") ?? "SYNAPSOR_RUNNER_HTTP_TOKEN";
9577
9669
  if (!await fileExists(rawConfigPath)) {
9578
9670
  process2.stderr.write(`Warning: config path does not exist yet: ${rawConfigPath}
9579
9671
  `);
9580
9672
  }
9581
- if (!path3.isAbsolute(configPath) || !path3.isAbsolute(storePath)) {
9673
+ if (transport === "stdio" && (!path3.isAbsolute(configPath) || !path3.isAbsolute(storePath))) {
9582
9674
  process2.stderr.write("Warning: relative paths are resolved by the MCP client working directory. Use --absolute-paths if the client runs from another directory.\n");
9583
9675
  }
9584
- const snippet = mcpClientSnippet(client, configPath, storePath);
9676
+ const snippet = mcpClientSnippet(client, configPath, storePath, { transport, aliasMode, host, port, authTokenEnv });
9585
9677
  if (args.includes("--write")) {
9586
9678
  const destination = optionalArg(args, "--destination");
9587
9679
  if (!destination) throw new Error("mcp configure --write requires --destination <path>");
@@ -9599,18 +9691,99 @@ async function mcpConfigure(args) {
9599
9691
  async function mcpConfig(args) {
9600
9692
  const [client, ...rest] = args;
9601
9693
  if (!client || client.startsWith("--")) return mcpConfigure(["--client", "claude-desktop", ...args]);
9602
- return mcpConfigure(["--client", client, ...rest]);
9694
+ return mcpConfigure(["--client", normalizeMcpClientName(client) ?? client, ...rest]);
9695
+ }
9696
+ function normalizeMcpClientName(client) {
9697
+ if (client === "claude") return "claude-desktop";
9698
+ return client;
9699
+ }
9700
+ function mcpClientConfigTransport(args, client) {
9701
+ const requested = optionalArg(args, "--transport") ?? (client === "openai-agents" ? "streamable-http" : "stdio");
9702
+ if (requested === "stdio" || requested === "streamable-http") return requested;
9703
+ if (requested === "http" || requested === "json-rpc-http" || requested === "jsonrpc-http") {
9704
+ throw new Error("mcp config uses stdio or streamable-http. The lightweight JSON-RPC HTTP bridge is not a standard MCP client transport.");
9705
+ }
9706
+ throw new Error("--transport must be stdio or streamable-http");
9707
+ }
9708
+ function mcpClientConfigAliasMode(args, client) {
9709
+ const requested = optionalArg(args, "--alias-mode");
9710
+ const aliasMode = requested ?? (args.includes("--openai-tool-aliases") ? "openai" : client === "openai-agents" ? "openai" : "canonical");
9711
+ if (aliasMode === "canonical" || aliasMode === "openai" || aliasMode === "both") return aliasMode;
9712
+ throw new Error("--alias-mode must be canonical, openai, or both");
9603
9713
  }
9604
- function mcpClientSnippet(client, configPath, storePath) {
9714
+ function serveArgsForClient(configPath, storePath, options) {
9715
+ const args = options.transport === "streamable-http" ? [
9716
+ "mcp",
9717
+ "serve-streamable-http",
9718
+ "--config",
9719
+ configPath,
9720
+ "--store",
9721
+ storePath,
9722
+ "--host",
9723
+ options.host,
9724
+ "--port",
9725
+ String(options.port),
9726
+ "--auth-token-env",
9727
+ options.authTokenEnv
9728
+ ] : ["mcp", "serve", "--config", configPath, "--store", storePath];
9729
+ if (options.aliasMode !== "canonical") args.push("--alias-mode", options.aliasMode);
9730
+ return args;
9731
+ }
9732
+ function mcpClientSnippet(client, configPath, storePath, options) {
9605
9733
  const command = cliCommandName();
9606
- const args = ["mcp", "serve", "--config", configPath, "--store", storePath];
9734
+ const args = serveArgsForClient(configPath, storePath, options);
9607
9735
  if (client === "generic" || client === "generic-stdio") return { command, args };
9608
9736
  if (client === "claude-desktop" || client === "cursor") {
9737
+ if (options.transport !== "stdio") throw new Error(`${client} config output currently supports stdio. Use --transport stdio.`);
9609
9738
  return { mcpServers: { synapsor: { command, args } } };
9610
9739
  }
9611
9740
  if (client === "vscode") {
9741
+ if (options.transport !== "stdio") throw new Error("vscode config output currently supports stdio. Use --transport stdio.");
9612
9742
  return { servers: { synapsor: { type: "stdio", command, args } } };
9613
9743
  }
9744
+ if (client === "openai-agents") {
9745
+ if (options.transport !== "streamable-http") throw new Error("openai-agents config output uses Streamable HTTP. Use --transport streamable-http.");
9746
+ const url = `http://${options.host}:${options.port}/mcp`;
9747
+ return {
9748
+ transport: "streamable-http",
9749
+ start_server: {
9750
+ command,
9751
+ args,
9752
+ env: {
9753
+ [options.authTokenEnv]: "<set-a-random-local-token>"
9754
+ }
9755
+ },
9756
+ openai_agents_sdk: {
9757
+ package: "openai-agents",
9758
+ url,
9759
+ headers_from_env: {
9760
+ Authorization: `Bearer $${options.authTokenEnv}`
9761
+ },
9762
+ python: [
9763
+ "import os",
9764
+ "from agents.mcp import MCPServerStreamableHttp",
9765
+ "",
9766
+ "synapsor_mcp = MCPServerStreamableHttp(",
9767
+ ` params={`,
9768
+ ` "url": "${url}",`,
9769
+ ` "headers": {"Authorization": f"Bearer {os.environ['${options.authTokenEnv}']}"},`,
9770
+ " }",
9771
+ ")"
9772
+ ].join("\n")
9773
+ },
9774
+ tool_names: {
9775
+ canonical: "billing.inspect_invoice",
9776
+ model_visible_with_alias_mode_openai: "billing__inspect_invoice",
9777
+ alias_mode: options.aliasMode
9778
+ },
9779
+ notes: [
9780
+ "Start the local Streamable HTTP MCP server before creating the OpenAI Agents SDK server.",
9781
+ "OpenAI-facing configs should use --alias-mode openai because OpenAI function names cannot contain dots.",
9782
+ "Runner maps aliases back to canonical Synapsor capability names and includes the canonical name in MCP tool metadata.",
9783
+ "This config contains no database URLs, write credentials, API keys, or bearer token values."
9784
+ ]
9785
+ };
9786
+ }
9614
9787
  throw new Error(`unsupported MCP client: ${client}`);
9615
9788
  }
9616
9789
  async function mcpSmoke(args) {
@@ -9659,7 +9832,9 @@ async function toolsPreview(args) {
9659
9832
  ok: boundary.ok,
9660
9833
  config_path: boundary.configPath,
9661
9834
  store_path: boundary.storePath,
9835
+ alias_mode: boundary.aliasMode,
9662
9836
  exposed_to_mcp: boundary.names,
9837
+ alias_mappings: boundary.exposures,
9663
9838
  not_exposed_to_mcp: defaultBlockedToolSurface(),
9664
9839
  checks: boundary.checks
9665
9840
  }, null, 2)}
@@ -9672,6 +9847,7 @@ async function toolsPreview(args) {
9672
9847
  async function inspectMcpToolBoundary(args) {
9673
9848
  const configPath = optionalArg(args, "--config") ?? "./synapsor.runner.json";
9674
9849
  const storePath = optionalArg(args, "--store") ?? "./.synapsor/local.db";
9850
+ const aliasMode = args.includes("--aliases") && !optionalArg(args, "--alias-mode") && !optionalArg(args, "--tool-name-style") ? "both" : toolNameStyleOption(args);
9675
9851
  if (!await fileExists(configPath)) {
9676
9852
  throw new Error(`MCP tool preview could not find ${configPath}.
9677
9853
 
@@ -9685,7 +9861,8 @@ Run ${cliCommandName()} onboard db --from-env DATABASE_URL, or pass --config <pa
9685
9861
  const runtime = createMcpRuntime(parsed, { storePath });
9686
9862
  try {
9687
9863
  const tools2 = runtime.listTools();
9688
- const names = tools2.map((tool) => tool.name);
9864
+ const exposures = toolNameExposures(tools2.map((tool) => tool.name), aliasMode);
9865
+ const names = exposures.map((item) => item.exposedName);
9689
9866
  const serialized = JSON.stringify(tools2);
9690
9867
  const checks = [
9691
9868
  { name: "semantic tools present", ok: names.length > 0, detail: names.join(", ") || "none" },
@@ -9696,7 +9873,7 @@ Run ${cliCommandName()} onboard db --from-env DATABASE_URL, or pass --config <pa
9696
9873
  { name: "write credentials absent", ok: !/(password|secret|bearer|private[_-]?key|token)/i.test(serialized), detail: "MCP tools do not include write credentials" }
9697
9874
  ];
9698
9875
  const ok = checks.every((check) => check.ok);
9699
- return { ok, configPath, storePath, names, checks };
9876
+ return { ok, configPath, storePath, aliasMode, names, exposures, checks };
9700
9877
  } finally {
9701
9878
  runtime.close();
9702
9879
  }
@@ -9713,13 +9890,15 @@ function defaultBlockedToolSurface() {
9713
9890
  ];
9714
9891
  }
9715
9892
  function formatToolsPreview(input) {
9893
+ const exposedLines = input.exposures.length > 0 ? input.exposures.map((item) => item.isAlias ? ` - ${item.exposedName} -> ${item.canonicalName}` : ` - ${item.exposedName}`) : [" - (none)"];
9716
9894
  const lines = [
9717
9895
  `Synapsor tools preview: ${input.ok ? "ok" : "failed"}`,
9718
9896
  `Config: ${input.configPath}`,
9719
9897
  `Store: ${input.storePath}`,
9898
+ `Alias mode: ${input.aliasMode}`,
9720
9899
  "",
9721
9900
  "Exposed to MCP:",
9722
- ...input.names.length > 0 ? input.names.map((name) => ` - ${name}`) : [" - (none)"],
9901
+ ...exposedLines,
9723
9902
  "",
9724
9903
  "Not exposed to MCP:",
9725
9904
  ...defaultBlockedToolSurface().map((name) => ` - ${name}`),
@@ -11397,7 +11576,7 @@ function databaseInputFromArgs(args) {
11397
11576
  if (inlineUrl && !isDatabaseUrl(inlineUrl)) {
11398
11577
  throw new Error("--from must be a postgres://, postgresql://, or mysql:// URL.");
11399
11578
  }
11400
- const fromEnv = optionalArg(args, "--from-env") ?? optionalArg(args, "--database-url-env");
11579
+ const fromEnv = optionalArg(args, "--from-env") ?? optionalArg(args, "--url-env") ?? optionalArg(args, "--database-url-env");
11401
11580
  const configDatabaseUrlEnv = fromEnv ?? "SYNAPSOR_DATABASE_READ_URL";
11402
11581
  if (inlineUrl) {
11403
11582
  return {
@@ -11443,6 +11622,7 @@ function firstPositional(args) {
11443
11622
  "--format",
11444
11623
  "--from",
11445
11624
  "--from-env",
11625
+ "--url-env",
11446
11626
  "--host",
11447
11627
  "--idempotency-key",
11448
11628
  "--input",
@@ -12371,7 +12551,7 @@ function starterCloudConfig() {
12371
12551
  base_url_env: "SYNAPSOR_CLOUD_BASE_URL",
12372
12552
  runner_token_env: "SYNAPSOR_RUNNER_TOKEN",
12373
12553
  runner_id: "synapsor_runner_local",
12374
- runner_version: "0.1.0-alpha.10",
12554
+ runner_version: "0.1.0-alpha.11",
12375
12555
  project_id: "token_scope",
12376
12556
  adapter_id: "mcp.your_adapter",
12377
12557
  source_id: "src_replace_me",
@@ -12492,12 +12672,14 @@ so it is not confused with first-run onboarding.
12492
12672
  `,
12493
12673
  inspect: `Usage:
12494
12674
  ${cmd} inspect --from-env DATABASE_URL [--engine auto|postgres|mysql] [--schema public] [--json]
12675
+ ${cmd} inspect --engine postgres --url-env DATABASE_URL
12495
12676
  ${cmd} inspect "<postgres-or-mysql-url>" [--engine auto|postgres|mysql] [--schema public] [--json]
12496
12677
 
12497
12678
  Inspect schema metadata without mutating the database or printing credentials.
12498
12679
  `,
12499
12680
  init: `Usage:
12500
12681
  ${cmd} init --wizard --from-env DATABASE_URL [--mode read_only|review|shadow] [--out synapsor.runner.json]
12682
+ ${cmd} init --engine postgres --url-env DATABASE_URL --mode review --table public.invoices
12501
12683
  ${cmd} init --inspection-json schema.json --table invoices --mode review --patch-from-arg waiver_reason=reason
12502
12684
  ${cmd} init --inspection-json schema.json --table invoices --mode review --writeback http_handler --handler-url-env APP_WRITEBACK_URL
12503
12685
 
@@ -12506,9 +12688,11 @@ Review mode writeback choices: sql_update, http_handler, command_handler.
12506
12688
  `,
12507
12689
  mcp: `Usage:
12508
12690
  ${cmd} mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db
12691
+ ${cmd} mcp serve --transport streamable-http --config ./synapsor.runner.json --store ./.synapsor/local.db --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
12509
12692
  ${cmd} mcp serve-streamable-http --config ./synapsor.runner.json --store ./.synapsor/local.db --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
12510
12693
  ${cmd} mcp serve-http --config ./synapsor.runner.json --store ./.synapsor/local.db --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
12511
12694
  ${cmd} mcp config --absolute-paths --config ./synapsor.runner.json --store ./.synapsor/local.db
12695
+ ${cmd} mcp client-config --client openai-agents --config ./synapsor.runner.json --store ./.synapsor/local.db
12512
12696
  ${cmd} mcp audit --example dangerous-db-mcp
12513
12697
  ${cmd} mcp audit ./tools-list.json
12514
12698
 
@@ -12516,19 +12700,24 @@ Use stdio for local MCP clients that launch the runner. Use Streamable HTTP for
12516
12700
  MCP clients see semantic tools. They do not receive raw SQL, write credentials, approval tools, or commit tools.
12517
12701
  `,
12518
12702
  "mcp serve": `Usage:
12519
- ${cmd} mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db [--read-only] [--local]
12703
+ ${cmd} mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db [--transport stdio] [--read-only] [--local] [--alias-mode canonical|openai|both]
12704
+ ${cmd} mcp serve --transport streamable-http --config ./synapsor.runner.json --store ./.synapsor/local.db --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
12520
12705
 
12521
12706
  Start the stdio MCP server for local MCP clients such as Claude Desktop, Cursor, or local agent tools. Startup logs stay off stdout so the MCP protocol remains clean.
12707
+ Use --alias-mode openai, or --openai-tool-aliases, for clients that reject dotted tool names. Use --alias-mode both to expose canonical and alias names.
12522
12708
  `,
12523
12709
  "mcp serve-streamable-http": `Usage:
12524
12710
  export SYNAPSOR_RUNNER_HTTP_TOKEN=...
12525
- ${cmd} mcp serve-streamable-http --config ./synapsor.runner.json --store ./.synapsor/local.db [--host 127.0.0.1] [--port 8766] [--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN]
12711
+ ${cmd} mcp serve-streamable-http --config ./synapsor.runner.json --store ./.synapsor/local.db [--host 127.0.0.1] [--port 8766] [--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN] [--alias-mode canonical|openai|both]
12526
12712
 
12527
12713
  Start the spec-compatible MCP Streamable HTTP endpoint for clients and SDKs that support HTTP MCP.
12528
12714
  Bearer auth is required by default.
12529
12715
 
12530
12716
  Alpha scope:
12531
12717
  - Supports MCP initialize/session behavior through the official MCP Streamable HTTP transport.
12718
+ - Use --alias-mode openai, or --openai-tool-aliases, for clients that reject dotted tool names.
12719
+ - Use --alias-mode both to expose canonical names and aliases.
12720
+ - OpenAI aliases expose names such as billing__inspect_invoice while preserving the canonical Synapsor name in _meta.
12532
12721
  - Use /mcp for the MCP endpoint and /healthz for service health.
12533
12722
  - Sessions are in-memory. Restarting the runner clears active HTTP MCP sessions.
12534
12723
 
@@ -12557,9 +12746,19 @@ Security:
12557
12746
  - Optional CORS: --cors-origin http://localhost:3000
12558
12747
  `,
12559
12748
  "mcp config": `Usage:
12560
- ${cmd} mcp config [claude-desktop|cursor|generic|vscode] [--absolute-paths] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12749
+ ${cmd} mcp config [claude-desktop|cursor|generic|vscode|openai-agents] [--absolute-paths] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12750
+ ${cmd} mcp client-config --client openai-agents [--transport streamable-http] [--port 8766] [--alias-mode openai] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12561
12751
 
12562
12752
  Print MCP client configuration that references the local runner command, not database URLs. Defaults to claude-desktop.
12753
+ OpenAI Agents SDK output uses Streamable HTTP and OpenAI-safe aliases by default.
12754
+ `,
12755
+ "mcp client-config": `Usage:
12756
+ ${cmd} mcp client-config --client claude-desktop [--absolute-paths] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12757
+ ${cmd} mcp client-config --client cursor [--absolute-paths] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12758
+ ${cmd} mcp client-config --client openai-agents [--transport streamable-http] [--port 8766] [--alias-mode openai] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
12759
+
12760
+ Print MCP client configuration that references the local runner command, not database URLs.
12761
+ OpenAI Agents SDK output uses Streamable HTTP and OpenAI-safe aliases by default.
12563
12762
  `,
12564
12763
  smoke: `Usage:
12565
12764
  ${cmd} smoke call [capability-name] [--sample] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]