@ory/mcp-server 0.10.0 → 0.11.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.
- package/README.md +4 -0
- package/dist/server.js +50 -3
- package/dist/tools/api.d.ts +35 -0
- package/dist/tools/api.js +81 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,6 +78,10 @@ CLI-backed tools accept optional `project` and `workspace` parameters; defaults
|
|
|
78
78
|
|
|
79
79
|
Per-call parameters on `ory_api_request` (`base_url`, `api_key`) override the corresponding env vars.
|
|
80
80
|
|
|
81
|
+
### Trusted hosts for `ory_api_request`
|
|
82
|
+
|
|
83
|
+
The env-configured credential (`ORY_API_KEY` / `ORY_ACCESS_TOKEN`) is only attached automatically when the request targets a trusted Ory origin: the origin of the configured `ORY_PROJECT_URL` / `ORY_SDK_URL`, an Ory Network host (`*.projects.oryapis.com` / `*.oryapis.com`) or `api.console.ory.sh` over https, or localhost (any port, for the local-dev stack). If a caller-supplied `base_url` points anywhere else, the request is still sent but without the env credential, and the tool result notes that it was withheld — this prevents a prompt-injected agent from exfiltrating your admin API key to an arbitrary host. An explicitly passed `api_key` parameter or `authorization` header is always honored as-is.
|
|
84
|
+
|
|
81
85
|
## Links
|
|
82
86
|
|
|
83
87
|
- [ory.com](https://ory.com)
|
package/dist/server.js
CHANGED
|
@@ -6,8 +6,43 @@
|
|
|
6
6
|
* identities, OAuth2, relationships, projects, configuration, workspaces,
|
|
7
7
|
* organizations, JWKs, event streams, and a generic REST API request.
|
|
8
8
|
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
9
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
43
|
exports.createOryMcpServer = createOryMcpServer;
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
11
46
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
12
47
|
const identities_js_1 = require("./tools/identities.js");
|
|
13
48
|
const oauth2_js_1 = require("./tools/oauth2.js");
|
|
@@ -19,9 +54,21 @@ const organizations_js_1 = require("./tools/organizations.js");
|
|
|
19
54
|
const jwk_js_1 = require("./tools/jwk.js");
|
|
20
55
|
const event_streams_js_1 = require("./tools/event_streams.js");
|
|
21
56
|
const api_js_1 = require("./tools/api.js");
|
|
22
|
-
// Read version from package.json
|
|
23
|
-
//
|
|
24
|
-
|
|
57
|
+
// Read the version from this package's own package.json so the reported MCP
|
|
58
|
+
// server version always matches the published package. Works from both the
|
|
59
|
+
// compiled dist/ layout and ts-node/vitest src/ layout (../package.json in
|
|
60
|
+
// either case); falls back to 0.0.0 if the file cannot be read.
|
|
61
|
+
function readPackageVersion() {
|
|
62
|
+
try {
|
|
63
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
65
|
+
return pkg.version ?? "0.0.0";
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return "0.0.0";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const VERSION = readPackageVersion();
|
|
25
72
|
function createOryMcpServer() {
|
|
26
73
|
const server = new mcp_js_1.McpServer({
|
|
27
74
|
name: "ory",
|
package/dist/tools/api.d.ts
CHANGED
|
@@ -1,2 +1,37 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
3
|
+
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
4
|
+
interface ApiCallParams {
|
|
5
|
+
method: HttpMethod;
|
|
6
|
+
path: string;
|
|
7
|
+
base_url?: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
query?: Record<string, string>;
|
|
11
|
+
api_key?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Credential-attachment guard.
|
|
15
|
+
*
|
|
16
|
+
* The env-configured admin credential (ORY_API_KEY / ORY_ACCESS_TOKEN) is only
|
|
17
|
+
* auto-attached when the request targets a trusted origin. Otherwise a
|
|
18
|
+
* prompt-injected caller could pass base_url="https://attacker.example" and
|
|
19
|
+
* exfiltrate the Ory Network admin token.
|
|
20
|
+
*
|
|
21
|
+
* Trusted origins:
|
|
22
|
+
* - The origin of the env-configured project URL (ORY_PROJECT_URL / ORY_SDK_URL).
|
|
23
|
+
* - Ory Network hosts over https: *.projects.oryapis.com / *.oryapis.com
|
|
24
|
+
* (and oryapis.com itself) and api.console.ory.sh.
|
|
25
|
+
* - Loopback hosts (localhost / 127.0.0.1 / ::1) over http or https on any
|
|
26
|
+
* port, for the local-dev Ory stack (e.g. http://localhost:4000).
|
|
27
|
+
*/
|
|
28
|
+
export declare function isTrustedOryOrigin(target: URL): boolean;
|
|
29
|
+
export declare function executeApiCall(params: ApiCallParams): Promise<{
|
|
30
|
+
content: Array<{
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}>;
|
|
34
|
+
isError?: boolean;
|
|
35
|
+
}>;
|
|
2
36
|
export declare function registerApiTools(server: McpServer): void;
|
|
37
|
+
export {};
|
package/dist/tools/api.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isTrustedOryOrigin = isTrustedOryOrigin;
|
|
4
|
+
exports.executeApiCall = executeApiCall;
|
|
3
5
|
exports.registerApiTools = registerApiTools;
|
|
4
6
|
const zod_1 = require("zod");
|
|
5
7
|
const logger_js_1 = require("../logger.js");
|
|
@@ -9,8 +11,51 @@ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5 MB
|
|
|
9
11
|
function resolveBaseUrl(explicit) {
|
|
10
12
|
return explicit ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL ?? null;
|
|
11
13
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
14
|
+
function resolveEnvApiKey() {
|
|
15
|
+
return process.env.ORY_API_KEY ?? process.env.ORY_ACCESS_TOKEN;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Credential-attachment guard.
|
|
19
|
+
*
|
|
20
|
+
* The env-configured admin credential (ORY_API_KEY / ORY_ACCESS_TOKEN) is only
|
|
21
|
+
* auto-attached when the request targets a trusted origin. Otherwise a
|
|
22
|
+
* prompt-injected caller could pass base_url="https://attacker.example" and
|
|
23
|
+
* exfiltrate the Ory Network admin token.
|
|
24
|
+
*
|
|
25
|
+
* Trusted origins:
|
|
26
|
+
* - The origin of the env-configured project URL (ORY_PROJECT_URL / ORY_SDK_URL).
|
|
27
|
+
* - Ory Network hosts over https: *.projects.oryapis.com / *.oryapis.com
|
|
28
|
+
* (and oryapis.com itself) and api.console.ory.sh.
|
|
29
|
+
* - Loopback hosts (localhost / 127.0.0.1 / ::1) over http or https on any
|
|
30
|
+
* port, for the local-dev Ory stack (e.g. http://localhost:4000).
|
|
31
|
+
*/
|
|
32
|
+
function isTrustedOryOrigin(target) {
|
|
33
|
+
const hostname = target.hostname.toLowerCase();
|
|
34
|
+
// Loopback (local-dev stack) — any port, http or https.
|
|
35
|
+
if ((target.protocol === "http:" || target.protocol === "https:") &&
|
|
36
|
+
(hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]")) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Ory Network / Ory Console API hosts — https only.
|
|
40
|
+
if (target.protocol === "https:") {
|
|
41
|
+
if (hostname === "oryapis.com" || hostname.endsWith(".oryapis.com"))
|
|
42
|
+
return true;
|
|
43
|
+
if (hostname === "api.console.ory.sh")
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Same origin as the env-configured project URL.
|
|
47
|
+
for (const configured of [process.env.ORY_PROJECT_URL, process.env.ORY_SDK_URL]) {
|
|
48
|
+
if (!configured)
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
if (new URL(configured).origin === target.origin)
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Ignore malformed configured URLs.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
14
59
|
}
|
|
15
60
|
function buildUrl(baseUrl, path, query) {
|
|
16
61
|
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
@@ -34,14 +79,35 @@ async function executeApiCall(params) {
|
|
|
34
79
|
isError: true,
|
|
35
80
|
};
|
|
36
81
|
}
|
|
37
|
-
const apiKey = resolveApiKey(params.api_key);
|
|
38
82
|
const url = buildUrl(baseUrl, params.path, params.query);
|
|
83
|
+
const targetUrl = new URL(url);
|
|
39
84
|
const headers = {
|
|
40
85
|
accept: "application/json",
|
|
41
86
|
...(params.headers ?? {}),
|
|
42
87
|
};
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
let credentialNote;
|
|
89
|
+
if (!headers.authorization && !headers.Authorization) {
|
|
90
|
+
if (params.api_key) {
|
|
91
|
+
// Explicitly caller-supplied credential — always honored.
|
|
92
|
+
headers.authorization = `Bearer ${params.api_key}`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const envKey = resolveEnvApiKey();
|
|
96
|
+
if (envKey) {
|
|
97
|
+
if (isTrustedOryOrigin(targetUrl)) {
|
|
98
|
+
headers.authorization = `Bearer ${envKey}`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
credentialNote =
|
|
102
|
+
`Note: the env-configured Ory credential (ORY_API_KEY / ORY_ACCESS_TOKEN) was NOT attached because ` +
|
|
103
|
+
`${targetUrl.origin} is not a trusted Ory host (project URL origin, *.oryapis.com, api.console.ory.sh, or localhost). ` +
|
|
104
|
+
`Pass 'api_key' or an 'authorization' header explicitly to authenticate against this host.`;
|
|
105
|
+
(0, logger_js_1.log)("warn", "withholding env credential for untrusted host", {
|
|
106
|
+
origin: targetUrl.origin,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
45
111
|
}
|
|
46
112
|
if (params.body && !headers["content-type"] && !headers["Content-Type"]) {
|
|
47
113
|
headers["content-type"] = "application/json";
|
|
@@ -96,10 +162,14 @@ async function executeApiCall(params) {
|
|
|
96
162
|
statusText: response.statusText,
|
|
97
163
|
body: text,
|
|
98
164
|
};
|
|
165
|
+
const content = [
|
|
166
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
167
|
+
];
|
|
168
|
+
if (credentialNote) {
|
|
169
|
+
content.push({ type: "text", text: credentialNote });
|
|
170
|
+
}
|
|
99
171
|
return {
|
|
100
|
-
content
|
|
101
|
-
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
102
|
-
],
|
|
172
|
+
content,
|
|
103
173
|
isError: !response.ok,
|
|
104
174
|
};
|
|
105
175
|
}
|
|
@@ -139,7 +209,9 @@ function registerApiTools(server) {
|
|
|
139
209
|
base_url: zod_1.z
|
|
140
210
|
.string()
|
|
141
211
|
.optional()
|
|
142
|
-
.describe("Override the base URL. Defaults to ORY_PROJECT_URL or ORY_SDK_URL env var."
|
|
212
|
+
.describe("Override the base URL. Defaults to ORY_PROJECT_URL or ORY_SDK_URL env var. " +
|
|
213
|
+
"The env-configured API key is only attached automatically when the URL targets a trusted Ory host " +
|
|
214
|
+
"(the configured project URL origin, *.oryapis.com, api.console.ory.sh over https, or localhost)."),
|
|
143
215
|
query: zod_1.z
|
|
144
216
|
.record(zod_1.z.string(), zod_1.z.string())
|
|
145
217
|
.optional()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|