@ory/mcp-server 0.14.0 → 1.0.1

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 CHANGED
@@ -9,7 +9,7 @@ Every Ory agent plugin in this repo registers it for you, but it's a standalone
9
9
  The server speaks MCP over stdio and registers ~50 tools across two execution paths:
10
10
 
11
11
  - **CLI-backed tools** shell out to the `ory` binary on your `PATH` (`--format json`). They cover identities, OAuth2 clients, relationships / permissions, projects, configuration, workspaces, organizations, JWKs, and event streams.
12
- - **`ory_api_request`** calls the Ory Network REST API directly over HTTP. Use it for any endpoint the dedicated tools don't cover (self-service flows, sessions, recovery codes, consent challenges, schemas). The CLI isn't required for this tool; just a base URL and API key.
12
+ - **`ory_api_request`** calls the Ory Network REST API directly over HTTP. Use it for any endpoint the dedicated tools don't cover (self-service flows, sessions, recovery codes, consent challenges, schemas). The base URL comes from the `ory` CLI's selected project unless you pin one, so this tool needs no configuration of its own; an API key is only required for endpoints that demand an admin credential.
13
13
 
14
14
  All logging goes to stderr; stdout is reserved for the MCP protocol.
15
15
 
@@ -17,7 +17,7 @@ All logging goes to stderr; stdout is reserved for the MCP protocol.
17
17
 
18
18
  - **Node.js ≥ 22.**
19
19
  - **The `ory` CLI on `PATH`** for the CLI-backed tools. Install from [ory.com/docs/guides/cli/installation](https://www.ory.com/docs/guides/cli/installation), then authenticate with `ory auth`. (Set `ORY_CLI_PATH` if the binary lives elsewhere.)
20
- - **`ORY_PROJECT_URL` + `ORY_API_KEY`** for the `ory_api_request` tool.
20
+ - **Nothing extra** for the `ory_api_request` tool — it falls back to the CLI's selected project for the base URL. Set `ORY_PROJECT_URL` to pin one, and `ORY_API_KEY` only for admin endpoints.
21
21
 
22
22
  ## Install
23
23
 
@@ -69,14 +69,14 @@ CLI-backed tools accept optional `project` and `workspace` parameters; defaults
69
69
  | `ORY_PROJECT` | _(none)_ | Default project ID/slug for CLI tools |
70
70
  | `ORY_WORKSPACE` | _(none)_ | Default workspace ID/name for CLI tools |
71
71
  | `ORY_CLI_TIMEOUT` | `30000` | CLI command timeout (ms) |
72
- | `ORY_PROJECT_URL` | _(none)_ | Base URL for `ory_api_request` |
72
+ | `ORY_PROJECT_URL` | _the `ory` CLI's selected project_ | Base URL for `ory_api_request`. Unset, the tool asks the CLI which project is selected (`ory use project`) and derives `https://<slug>.projects.oryapis.com` from it. `ORY_PROJECT` pins which project is looked up. |
73
73
  | `ORY_SDK_URL` | _(none)_ | Fallback base URL for `ory_api_request` when `ORY_PROJECT_URL` is unset |
74
- | `ORY_API_KEY` | _(none)_ | Bearer token for `ory_api_request` |
74
+ | `ORY_API_KEY` | _(none)_ | Bearer token for `ory_api_request`. Only needed for endpoints requiring an admin credential — the CI/headless path, not the default. |
75
75
  | `ORY_ACCESS_TOKEN` | _(none)_ | Fallback bearer for `ory_api_request` when `ORY_API_KEY` is unset |
76
76
  | `ORY_HTTP_TIMEOUT` | `30000` | REST request timeout (ms) |
77
77
  | `ORY_MCP_DEBUG` | `false` | Set to `true` for structured debug logging on stderr |
78
78
 
79
- Per-call parameters on `ory_api_request` (`base_url`, `api_key`) override the corresponding env vars.
79
+ Per-call parameters on `ory_api_request` (`base_url`, `api_key`) override the corresponding env vars, and the env vars override the CLI fallback.
80
80
 
81
81
  ### Trusted hosts for `ory_api_request`
82
82
 
@@ -10,6 +10,8 @@ interface ApiCallParams {
10
10
  query?: Record<string, string>;
11
11
  api_key?: string;
12
12
  }
13
+ /** Drop the cached CLI lookup. For tests. */
14
+ export declare function resetProjectUrlCache(): void;
13
15
  /**
14
16
  * Credential-attachment guard.
15
17
  *
package/dist/tools/api.js CHANGED
@@ -1,15 +1,99 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetProjectUrlCache = resetProjectUrlCache;
3
4
  exports.isTrustedOryOrigin = isTrustedOryOrigin;
4
5
  exports.executeApiCall = executeApiCall;
5
6
  exports.registerApiTools = registerApiTools;
6
7
  const zod_1 = require("zod");
7
8
  const logger_js_1 = require("../logger.js");
9
+ const executor_js_1 = require("../executor.js");
8
10
  const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
9
11
  const TIMEOUT = Number(process.env.ORY_HTTP_TIMEOUT) || 30_000;
10
12
  const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5 MB
11
- function resolveBaseUrl(explicit) {
12
- return explicit ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL ?? null;
13
+ /**
14
+ * The project URL, resolved from the `ory` CLI's selected project.
15
+ *
16
+ * `ORY_PROJECT_URL` / `ORY_SDK_URL` are **security-half** values: a project only
17
+ * sets them to connect Ory Agent Security. `ory_api_request` is a DX-half tool,
18
+ * and every other tool in this server already works off nothing but the CLI's
19
+ * own session — so requiring a security-half value here made one DX tool fail
20
+ * on a DX-only install against a real project, before a credential was even
21
+ * considered (#216).
22
+ *
23
+ * Two calls, because the CLI splits the answer: `ory use project` yields the
24
+ * selected project's **id**, and the SDK URL is **slug**-based. A project id is
25
+ * a UUID and is never substituted into the URL — that would produce a hostname
26
+ * that does not resolve.
27
+ *
28
+ * Everything here is best-effort. A missing CLI, an expired session, no
29
+ * selected project, or output that does not parse all collapse to `null`, and
30
+ * the caller reports the same "no base URL" error it always did.
31
+ */
32
+ async function resolveProjectUrlFromCli() {
33
+ const projectId = process.env.ORY_PROJECT?.trim() || (await selectedProjectId());
34
+ if (!projectId)
35
+ return null;
36
+ const project = await runOryJson(["get", "project", projectId, "--format", "json"]);
37
+ const slug = typeof project?.slug === "string" ? project.slug.trim() : "";
38
+ return slug ? `https://${slug}.projects.oryapis.com` : null;
39
+ }
40
+ async function selectedProjectId() {
41
+ const selected = await runOryJson(["use", "project", "--format", "json"]);
42
+ const id = typeof selected?.id === "string" ? selected.id.trim() : "";
43
+ // The CLI writes a zero UUID when nothing has been selected.
44
+ return id && !/^0{8}-0{4}-0{4}-0{4}-0{12}$/.test(id) ? id : null;
45
+ }
46
+ /**
47
+ * Run an `ory` command and parse its stdout as JSON, or `null`.
48
+ *
49
+ * The exit code is not sufficient on its own: `ory use project` with no
50
+ * selection prints `Error: no project was specified` and still exits 0. Parsing
51
+ * is therefore the test — anything that is not a JSON object is "no answer".
52
+ */
53
+ async function runOryJson(args) {
54
+ try {
55
+ const result = await (0, executor_js_1.runOryCommand)(args);
56
+ if (result.exitCode !== 0)
57
+ return null;
58
+ const parsed = JSON.parse(result.stdout);
59
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
60
+ ? parsed
61
+ : null;
62
+ }
63
+ catch {
64
+ // No CLI on PATH, a timeout, or non-JSON output. All mean "cannot answer".
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * Resolved project URL, cached for the life of the process.
70
+ *
71
+ * The lookup costs up to two subprocesses and `ory_api_request` is called
72
+ * repeatedly in a session, so a successful answer is cached. The in-flight
73
+ * promise is what is stored, so concurrent calls share one lookup.
74
+ *
75
+ * A **failure is deliberately not cached.** This server is long-lived — one
76
+ * stdio process per harness session — and the error it returns tells the user to
77
+ * run `ory use project`. Caching the failure would make that instruction false
78
+ * until they restarted the harness. The cost of re-trying is a couple of spawns
79
+ * on a path that is already returning an error, and a missing binary fails fast
80
+ * with ENOENT rather than waiting out the timeout.
81
+ */
82
+ let cliProjectUrl;
83
+ /** Drop the cached CLI lookup. For tests. */
84
+ function resetProjectUrlCache() {
85
+ cliProjectUrl = undefined;
86
+ }
87
+ async function resolveBaseUrl(explicit) {
88
+ const fromEnv = explicit ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL;
89
+ if (fromEnv)
90
+ return fromEnv;
91
+ const pending = (cliProjectUrl ??= resolveProjectUrlFromCli());
92
+ const resolved = await pending;
93
+ // Only a real answer sticks; see above.
94
+ if (resolved === null && cliProjectUrl === pending)
95
+ cliProjectUrl = undefined;
96
+ return resolved;
13
97
  }
14
98
  function resolveEnvApiKey() {
15
99
  return process.env.ORY_API_KEY ?? process.env.ORY_ACCESS_TOKEN;
@@ -67,13 +151,14 @@ function buildUrl(baseUrl, path, query) {
67
151
  return url.toString();
68
152
  }
69
153
  async function executeApiCall(params) {
70
- const baseUrl = resolveBaseUrl(params.base_url);
154
+ const baseUrl = await resolveBaseUrl(params.base_url);
71
155
  if (!baseUrl) {
72
156
  return {
73
157
  content: [
74
158
  {
75
159
  type: "text",
76
- text: "No base URL is configured. Set the ORY_PROJECT_URL environment variable or pass 'base_url'.",
160
+ text: "No base URL is configured. Select a project with 'ory use project <id>', " +
161
+ "set the ORY_PROJECT_URL environment variable, or pass 'base_url'.",
77
162
  },
78
163
  ],
79
164
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/mcp-server",
3
- "version": "0.14.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server exposing the Ory CLI and Ory Network REST API as tools for AI agents to manage identities, OAuth2 clients, permissions, projects, and configuration",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ory.com",
@@ -56,7 +56,6 @@
56
56
  ],
57
57
  "dependencies": {
58
58
  "@modelcontextprotocol/sdk": "^1.29.0",
59
- "reo-census": "^1.2.8",
60
59
  "zod": "^4.4.3"
61
60
  },
62
61
  "engines": {