@ory/mcp-server 1.5.3 → 1.7.0

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.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The Ory Network project the Ory Agent plugins are connected to.
3
+ *
4
+ * Connecting a plugin writes the project to the shared plugin config
5
+ * (`config.json` in the `@ory/argus` data directory), and `ORY_PROJECT_URL` /
6
+ * `ORY_SDK_URL` / `ORY_PROJECT_ID` override it, with the same precedence
7
+ * `@ory/argus` uses. This server reads only those fields, so it follows the
8
+ * connected project without depending on the plugin runtime, and the `ory` CLI
9
+ * is pointed at it with `--project` rather than with its own selected project.
10
+ */
11
+ export interface ConnectedProject {
12
+ /** Slug-based project URL, e.g. `https://<slug>.projects.oryapis.com`. */
13
+ url?: string;
14
+ /** Value for the CLI's `--project` flag: the project id, or the slug from the URL. */
15
+ cliProject?: string;
16
+ }
17
+ /**
18
+ * The project slug in a project URL: the label before `projects` in
19
+ * `<slug>.projects.<domain>`, which covers production, staging, and local
20
+ * Ory Network hosts. Custom domains carry no slug and yield `undefined`.
21
+ */
22
+ export declare function projectSlugFromUrl(url: string): string | undefined;
23
+ /** Read on every call: the plugin can be connected or switched while this server runs. */
24
+ export declare function connectedProject(): ConnectedProject;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * The Ory Network project the Ory Agent plugins are connected to.
4
+ *
5
+ * Connecting a plugin writes the project to the shared plugin config
6
+ * (`config.json` in the `@ory/argus` data directory), and `ORY_PROJECT_URL` /
7
+ * `ORY_SDK_URL` / `ORY_PROJECT_ID` override it, with the same precedence
8
+ * `@ory/argus` uses. This server reads only those fields, so it follows the
9
+ * connected project without depending on the plugin runtime, and the `ory` CLI
10
+ * is pointed at it with `--project` rather than with its own selected project.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.projectSlugFromUrl = projectSlugFromUrl;
47
+ exports.connectedProject = connectedProject;
48
+ const fs = __importStar(require("node:fs"));
49
+ const os = __importStar(require("node:os"));
50
+ const path = __importStar(require("node:path"));
51
+ /** The shared plugin data directory, as `getDataDir` in `@ory/argus` resolves it. */
52
+ function pluginDataDir() {
53
+ const xdg = process.env.XDG_CONFIG_HOME?.trim();
54
+ if (xdg)
55
+ return path.join(xdg, "ory-agent-plugins");
56
+ if (process.platform === "win32") {
57
+ const appData = process.env.APPDATA?.trim() || path.join(os.homedir(), "AppData", "Roaming");
58
+ return path.join(appData, "ory-agent-plugins");
59
+ }
60
+ return path.join(os.homedir(), ".config", "ory-agent-plugins");
61
+ }
62
+ function readPluginConfig() {
63
+ try {
64
+ const parsed = JSON.parse(fs.readFileSync(path.join(pluginDataDir(), "config.json"), "utf-8"));
65
+ return parsed && typeof parsed === "object" ? parsed : {};
66
+ }
67
+ catch {
68
+ return {};
69
+ }
70
+ }
71
+ function nonEmpty(value) {
72
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
73
+ }
74
+ /**
75
+ * The project slug in a project URL: the label before `projects` in
76
+ * `<slug>.projects.<domain>`, which covers production, staging, and local
77
+ * Ory Network hosts. Custom domains carry no slug and yield `undefined`.
78
+ */
79
+ function projectSlugFromUrl(url) {
80
+ try {
81
+ const labels = new URL(url).hostname.toLowerCase().split(".");
82
+ return labels.length >= 3 && labels[1] === "projects" && labels[0] ? labels[0] : undefined;
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ /** Read on every call: the plugin can be connected or switched while this server runs. */
89
+ function connectedProject() {
90
+ const envUrl = nonEmpty(process.env.ORY_PROJECT_URL) ?? nonEmpty(process.env.ORY_SDK_URL);
91
+ const envId = nonEmpty(process.env.ORY_PROJECT_ID);
92
+ // An env URL names a project the config file's id may not belong to.
93
+ const file = envUrl ? {} : readPluginConfig();
94
+ const url = envUrl ?? nonEmpty(file.projectUrl);
95
+ const id = envId ?? nonEmpty(file.projectId);
96
+ return { url, cliProject: id ?? (url ? projectSlugFromUrl(url) : undefined) };
97
+ }
@@ -22,11 +22,17 @@ export declare function runOryCommand(args: string[]): Promise<ExecResult>;
22
22
  /**
23
23
  * Build the common args appended to every ory command.
24
24
  * Adds --format json and optional --project / --workspace flags.
25
+ *
26
+ * The project defaults to `ORY_PROJECT`, then to the project the Ory Agent
27
+ * plugins are connected to, so the CLI follows the connected project instead of
28
+ * its own selected one. Commands that take no `--project` flag (workspaces)
29
+ * pass `projectScoped: false`.
25
30
  */
26
31
  export declare function buildCommonArgs(params: {
27
32
  project?: string;
28
33
  workspace?: string;
29
34
  format?: boolean;
35
+ projectScoped?: boolean;
30
36
  }): string[];
31
37
  /**
32
38
  * Helper to format an ory command result as an MCP tool response.
package/dist/executor.js CHANGED
@@ -50,6 +50,7 @@ const fs = __importStar(require("node:fs"));
50
50
  const os = __importStar(require("node:os"));
51
51
  const path = __importStar(require("node:path"));
52
52
  const node_util_1 = require("node:util");
53
+ const connected_project_js_1 = require("./connected-project.js");
53
54
  const logger_js_1 = require("./logger.js");
54
55
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
55
56
  const ORY_BINARY = process.env.ORY_CLI_PATH ?? "ory";
@@ -88,7 +89,9 @@ async function runOryCommand(args) {
88
89
  if (execErr.killed || execErr.signal === "SIGTERM") {
89
90
  return {
90
91
  stdout: "",
91
- stderr: `Command timed out after ${TIMEOUT}ms. Increase ORY_CLI_TIMEOUT if needed.`,
92
+ stderr: `Command timed out after ${TIMEOUT}ms. If the ory CLI opened a browser to sign in, ` +
93
+ "this server cannot wait for it: run 'ory auth' in a terminal, then retry. " +
94
+ "Otherwise increase ORY_CLI_TIMEOUT.",
92
95
  exitCode: 124,
93
96
  };
94
97
  }
@@ -106,13 +109,20 @@ async function runOryCommand(args) {
106
109
  /**
107
110
  * Build the common args appended to every ory command.
108
111
  * Adds --format json and optional --project / --workspace flags.
112
+ *
113
+ * The project defaults to `ORY_PROJECT`, then to the project the Ory Agent
114
+ * plugins are connected to, so the CLI follows the connected project instead of
115
+ * its own selected one. Commands that take no `--project` flag (workspaces)
116
+ * pass `projectScoped: false`.
109
117
  */
110
118
  function buildCommonArgs(params) {
111
119
  const args = [];
112
120
  if (params.format !== false) {
113
121
  args.push("--format", "json");
114
122
  }
115
- const project = params.project ?? process.env.ORY_PROJECT;
123
+ const project = params.projectScoped === false
124
+ ? undefined
125
+ : params.project ?? process.env.ORY_PROJECT ?? (0, connected_project_js_1.connectedProject)().cliProject;
116
126
  const workspace = params.workspace ?? process.env.ORY_WORKSPACE;
117
127
  if (project)
118
128
  args.push("--project", project);
@@ -21,7 +21,8 @@ export declare function resetProjectUrlCache(): void;
21
21
  * exfiltrate the Ory Network admin token.
22
22
  *
23
23
  * Trusted origins:
24
- * - The origin of the env-configured project URL (ORY_PROJECT_URL / ORY_SDK_URL).
24
+ * - The origin of the connected project URL (ORY_PROJECT_URL / ORY_SDK_URL, or
25
+ * the project the Ory Agent plugins are connected to).
25
26
  * - Ory Network hosts over https: *.projects.oryapis.com / *.oryapis.com
26
27
  * (and oryapis.com itself) and api.console.ory.sh.
27
28
  * - Loopback hosts (localhost / 127.0.0.1 / ::1) over http or https on any
package/dist/tools/api.js CHANGED
@@ -6,12 +6,14 @@ exports.executeApiCall = executeApiCall;
6
6
  exports.registerApiTools = registerApiTools;
7
7
  const zod_1 = require("zod");
8
8
  const logger_js_1 = require("../logger.js");
9
+ const connected_project_js_1 = require("../connected-project.js");
9
10
  const executor_js_1 = require("../executor.js");
10
11
  const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
11
12
  const TIMEOUT = Number(process.env.ORY_HTTP_TIMEOUT) || 30_000;
12
13
  const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5 MB
13
14
  /**
14
- * The project URL, resolved from the `ory` CLI's selected project.
15
+ * The project URL, resolved from the `ory` CLI's selected project. This is the
16
+ * fallback when no project is connected (see {@link connectedProject}).
15
17
  *
16
18
  * `ORY_PROJECT_URL` / `ORY_SDK_URL` are **security-half** values: a project only
17
19
  * sets them to connect Ory Agent Security. `ory_api_request` is a DX-half tool,
@@ -85,9 +87,9 @@ function resetProjectUrlCache() {
85
87
  cliProjectUrl = undefined;
86
88
  }
87
89
  async function resolveBaseUrl(explicit) {
88
- const fromEnv = explicit ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL;
89
- if (fromEnv)
90
- return fromEnv;
90
+ const configured = explicit ?? (0, connected_project_js_1.connectedProject)().url;
91
+ if (configured)
92
+ return configured;
91
93
  const pending = (cliProjectUrl ??= resolveProjectUrlFromCli());
92
94
  const resolved = await pending;
93
95
  // Only a real answer sticks; see above.
@@ -107,7 +109,8 @@ function resolveEnvApiKey() {
107
109
  * exfiltrate the Ory Network admin token.
108
110
  *
109
111
  * Trusted origins:
110
- * - The origin of the env-configured project URL (ORY_PROJECT_URL / ORY_SDK_URL).
112
+ * - The origin of the connected project URL (ORY_PROJECT_URL / ORY_SDK_URL, or
113
+ * the project the Ory Agent plugins are connected to).
111
114
  * - Ory Network hosts over https: *.projects.oryapis.com / *.oryapis.com
112
115
  * (and oryapis.com itself) and api.console.ory.sh.
113
116
  * - Loopback hosts (localhost / 127.0.0.1 / ::1) over http or https on any
@@ -127,10 +130,9 @@ function isTrustedOryOrigin(target) {
127
130
  if (hostname === "api.console.ory.sh")
128
131
  return true;
129
132
  }
130
- // Same origin as the env-configured project URL.
131
- for (const configured of [process.env.ORY_PROJECT_URL, process.env.ORY_SDK_URL]) {
132
- if (!configured)
133
- continue;
133
+ // Same origin as the connected project URL.
134
+ const configured = (0, connected_project_js_1.connectedProject)().url;
135
+ if (configured) {
134
136
  try {
135
137
  if (new URL(configured).origin === target.origin)
136
138
  return true;
@@ -283,7 +285,7 @@ async function executeApiCall(params) {
283
285
  function registerApiTools(server) {
284
286
  server.registerTool("ory_api_request", {
285
287
  title: "Ory REST API Request",
286
- description: "Make an authenticated request against the Ory Network REST API. Use this for endpoints not exposed by the dedicated tools (e.g. self-service flows, identity sessions, recovery codes, consent challenges, trusted JWT issuers, schemas). The base URL defaults to ORY_PROJECT_URL and authentication uses ORY_API_KEY as a Bearer token unless overridden. The full API reference is at https://ory.com/docs/reference/api.",
288
+ description: "Make an authenticated request against the Ory Network REST API. Use this for endpoints not exposed by the dedicated tools (e.g. self-service flows, identity sessions, recovery codes, consent challenges, trusted JWT issuers, schemas). The base URL defaults to the connected project (ORY_PROJECT_URL, or the project the Ory Agent plugins are connected to) and authentication uses ORY_API_KEY as a Bearer token unless overridden. The full API reference is at https://ory.com/docs/reference/api.",
287
289
  inputSchema: {
288
290
  method: zod_1.z
289
291
  .enum(HTTP_METHODS)
@@ -294,7 +296,7 @@ function registerApiTools(server) {
294
296
  base_url: zod_1.z
295
297
  .string()
296
298
  .optional()
297
- .describe("Override the base URL. Defaults to ORY_PROJECT_URL or ORY_SDK_URL env var. " +
299
+ .describe("Override the base URL. Defaults to ORY_PROJECT_URL / ORY_SDK_URL, then the connected project, then the ory CLI's selected project. " +
298
300
  "The env-configured API key is only attached automatically when the URL targets a trusted Ory host " +
299
301
  "(the configured project URL origin, *.oryapis.com, api.console.ory.sh over https, or localhost)."),
300
302
  query: zod_1.z
@@ -9,7 +9,7 @@ function registerWorkspaceTools(server) {
9
9
  description: "List Ory Network workspaces accessible to the current account.",
10
10
  inputSchema: {},
11
11
  }, async () => {
12
- const args = ["list", "workspaces", ...(0, executor_js_1.buildCommonArgs)({})];
12
+ const args = ["list", "workspaces", ...(0, executor_js_1.buildCommonArgs)({ projectScoped: false })];
13
13
  return (0, executor_js_1.execOryTool)(args);
14
14
  });
15
15
  server.registerTool("ory_get_workspace", {
@@ -19,7 +19,7 @@ function registerWorkspaceTools(server) {
19
19
  id: zod_1.z.string().describe("Workspace ID"),
20
20
  },
21
21
  }, async (params) => {
22
- const args = ["get", "workspace", params.id, ...(0, executor_js_1.buildCommonArgs)({})];
22
+ const args = ["get", "workspace", params.id, ...(0, executor_js_1.buildCommonArgs)({ projectScoped: false })];
23
23
  return (0, executor_js_1.execOryTool)(args);
24
24
  });
25
25
  server.registerTool("ory_create_workspace", {
@@ -34,7 +34,7 @@ function registerWorkspaceTools(server) {
34
34
  "workspace",
35
35
  "--name",
36
36
  params.name,
37
- ...(0, executor_js_1.buildCommonArgs)({}),
37
+ ...(0, executor_js_1.buildCommonArgs)({ projectScoped: false }),
38
38
  ];
39
39
  return (0, executor_js_1.execOryTool)(args);
40
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/mcp-server",
3
- "version": "1.5.3",
3
+ "version": "1.7.0",
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",
@@ -55,8 +55,8 @@
55
55
  "!dist/**/*.tsbuildinfo"
56
56
  ],
57
57
  "dependencies": {
58
- "@modelcontextprotocol/sdk": "^1.29.0",
59
- "zod": "^4.4.3"
58
+ "@modelcontextprotocol/sdk": "^1.32.2",
59
+ "zod": "^4.6.7"
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=22"