@ory/mcp-server 0.1.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 +87 -0
- package/dist/executor.d.ts +74 -0
- package/dist/executor.js +181 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +25 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +21 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +41 -0
- package/dist/tools/api.d.ts +2 -0
- package/dist/tools/api.js +161 -0
- package/dist/tools/config.d.ts +2 -0
- package/dist/tools/config.js +146 -0
- package/dist/tools/event_streams.d.ts +2 -0
- package/dist/tools/event_streams.js +97 -0
- package/dist/tools/identities.d.ts +2 -0
- package/dist/tools/identities.js +118 -0
- package/dist/tools/jwk.d.ts +2 -0
- package/dist/tools/jwk.js +110 -0
- package/dist/tools/oauth2.d.ts +2 -0
- package/dist/tools/oauth2.js +375 -0
- package/dist/tools/organizations.d.ts +2 -0
- package/dist/tools/organizations.js +87 -0
- package/dist/tools/projects.d.ts +2 -0
- package/dist/tools/projects.js +182 -0
- package/dist/tools/relationships.d.ts +2 -0
- package/dist/tools/relationships.js +181 -0
- package/dist/tools/workspaces.d.ts +2 -0
- package/dist/tools/workspaces.js +41 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Ory MCP Server
|
|
2
|
+
|
|
3
|
+
An [MCP](https://modelcontextprotocol.io) server that exposes the [Ory CLI](https://www.ory.com/docs/guides/cli/installation) and the [Ory Network REST API](https://www.ory.com/docs/reference/api) as tools your AI agent can call: manage identities, OAuth2 clients, permission relationships, projects, configuration, and more directly from a chat session.
|
|
4
|
+
|
|
5
|
+
Every Ory agent plugin in this repo registers it for you, but it's a standalone MCP server; install it on its own and point any MCP-capable harness at it.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
The server speaks MCP over stdio and registers ~50 tools across two execution paths:
|
|
10
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
All logging goes to stderr; stdout is reserved for the MCP protocol.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- **Node.js ≥ 24.**
|
|
19
|
+
- **The `ory` CLI on `PATH`** for the CLI-backed tools. Install from [ory.sh/docs/guides/cli/installation](https://www.ory.sh/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.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @ory/mcp-server
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then add it to your harness's MCP config. Most harnesses use the generic `mcpServers` shape; for example, Claude Code's `.claude/settings.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{ "mcpServers": { "ory": { "command": "ory-mcp-server" } } }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To always run the published version on demand instead of installing globally, point the command at `npx`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"ory": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["--package=@ory/mcp-server", "ory-mcp-server"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> Config shapes vary by harness. OpenCode, for example, uses a `mcp` (singular) key with a `{ "type": "local", "command": [...] }` connector. Check your harness's MCP documentation for the exact schema; the `ory-mcp-server` binary (or the `npx` invocation above) is what every shape ultimately runs.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
- **Identities**: `list`, `get`, `delete`, `import`, `validate`.
|
|
52
|
+
- **OAuth2 / OIDC**: `list`, `get`, `create`, `update`, `import`, `delete` clients; `introspect`, `revoke`, `perform_client_credentials`; `delete_access_tokens`.
|
|
53
|
+
- **Relationships & Permissions**: `list`, `create`, `create_from_file`, `parse`, `delete`, `check_permission`.
|
|
54
|
+
- **Projects**: `list`, `get`, `create`, `update`, `patch`, `use`.
|
|
55
|
+
- **Configuration**: `get`, `update`, `patch` for identity / oauth2 / permission configs; `update_opl`, `patch_opl`.
|
|
56
|
+
- **Workspaces**: `list`, `get`, `create`.
|
|
57
|
+
- **Organizations (B2B)**: `list`, `create`, `update`, `delete`.
|
|
58
|
+
- **JWKs**: `create`, `get`, `delete`, `import`.
|
|
59
|
+
- **Event streams**: `list`, `create`, `update`, `delete`.
|
|
60
|
+
- **Generic REST**: `ory_api_request` for any Ory Network REST endpoint.
|
|
61
|
+
|
|
62
|
+
CLI-backed tools accept optional `project` and `workspace` parameters; defaults come from `ORY_PROJECT` / `ORY_WORKSPACE` or from whatever `ory use project` selected. Tools that take a file (imports, OPL, config updates) accept either a `file` path or inline `content`.
|
|
63
|
+
|
|
64
|
+
## Environment
|
|
65
|
+
|
|
66
|
+
| Variable | Default | Description |
|
|
67
|
+
|----------|---------|-------------|
|
|
68
|
+
| `ORY_CLI_PATH` | `ory` | Path to the `ory` CLI binary |
|
|
69
|
+
| `ORY_PROJECT` | _(none)_ | Default project ID/slug for CLI tools |
|
|
70
|
+
| `ORY_WORKSPACE` | _(none)_ | Default workspace ID/name for CLI tools |
|
|
71
|
+
| `ORY_CLI_TIMEOUT` | `30000` | CLI command timeout (ms) |
|
|
72
|
+
| `ORY_PROJECT_URL` | _(none)_ | Base URL for `ory_api_request` |
|
|
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` |
|
|
75
|
+
| `ORY_ACCESS_TOKEN` | _(none)_ | Fallback bearer for `ory_api_request` when `ORY_API_KEY` is unset |
|
|
76
|
+
| `ORY_HTTP_TIMEOUT` | `30000` | REST request timeout (ms) |
|
|
77
|
+
| `ORY_MCP_DEBUG` | `false` | Set to `true` for structured debug logging on stderr |
|
|
78
|
+
|
|
79
|
+
Per-call parameters on `ory_api_request` (`base_url`, `api_key`) override the corresponding env vars.
|
|
80
|
+
|
|
81
|
+
## Links
|
|
82
|
+
|
|
83
|
+
- [ory.com](https://ory.com)
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
Apache-2.0
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ory CLI execution wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Runs `ory` subcommands via child_process.execFile and returns structured results.
|
|
5
|
+
* Always appends `--format json` for parseable output.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExecResult {
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
exitCode: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class OryCliNotFoundError extends Error {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run an ory CLI command and return the result.
|
|
17
|
+
*
|
|
18
|
+
* Non-zero exit codes are not thrown — the caller inspects stdout/stderr.
|
|
19
|
+
* Only truly fatal errors (binary missing, timeout) are thrown.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runOryCommand(args: string[]): Promise<ExecResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Build the common args appended to every ory command.
|
|
24
|
+
* Adds --format json and optional --project / --workspace flags.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildCommonArgs(params: {
|
|
27
|
+
project?: string;
|
|
28
|
+
workspace?: string;
|
|
29
|
+
format?: boolean;
|
|
30
|
+
}): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Helper to format an ory command result as an MCP tool response.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatToolResult(result: ExecResult): {
|
|
35
|
+
content: Array<{
|
|
36
|
+
type: "text";
|
|
37
|
+
text: string;
|
|
38
|
+
}>;
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Run an ory command and return a formatted MCP tool response.
|
|
43
|
+
* Catches OryCliNotFoundError and returns a user-friendly message.
|
|
44
|
+
*/
|
|
45
|
+
export declare function execOryTool(args: string[]): Promise<{
|
|
46
|
+
content: Array<{
|
|
47
|
+
type: "text";
|
|
48
|
+
text: string;
|
|
49
|
+
}>;
|
|
50
|
+
isError?: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* For CLI commands that accept either a file path or inline content,
|
|
54
|
+
* write the content to a tempfile and clean up afterwards.
|
|
55
|
+
*
|
|
56
|
+
* Returns an MCP error response if neither is provided. Pass `extension`
|
|
57
|
+
* to control the temp file suffix (e.g. ".json", ".ts").
|
|
58
|
+
*/
|
|
59
|
+
export declare function withFileOrContent(params: {
|
|
60
|
+
file?: string;
|
|
61
|
+
content?: string;
|
|
62
|
+
}, extension: string, fn: (filePath: string) => Promise<{
|
|
63
|
+
content: Array<{
|
|
64
|
+
type: "text";
|
|
65
|
+
text: string;
|
|
66
|
+
}>;
|
|
67
|
+
isError?: boolean;
|
|
68
|
+
}>): Promise<{
|
|
69
|
+
content: Array<{
|
|
70
|
+
type: "text";
|
|
71
|
+
text: string;
|
|
72
|
+
}>;
|
|
73
|
+
isError?: boolean;
|
|
74
|
+
}>;
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ory CLI execution wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Runs `ory` subcommands via child_process.execFile and returns structured results.
|
|
6
|
+
* Always appends `--format json` for parseable output.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.OryCliNotFoundError = void 0;
|
|
43
|
+
exports.runOryCommand = runOryCommand;
|
|
44
|
+
exports.buildCommonArgs = buildCommonArgs;
|
|
45
|
+
exports.formatToolResult = formatToolResult;
|
|
46
|
+
exports.execOryTool = execOryTool;
|
|
47
|
+
exports.withFileOrContent = withFileOrContent;
|
|
48
|
+
const node_child_process_1 = require("node:child_process");
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const os = __importStar(require("node:os"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const node_util_1 = require("node:util");
|
|
53
|
+
const logger_js_1 = require("./logger.js");
|
|
54
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
55
|
+
const ORY_BINARY = process.env.ORY_CLI_PATH ?? "ory";
|
|
56
|
+
const TIMEOUT = Number(process.env.ORY_CLI_TIMEOUT) || 30_000;
|
|
57
|
+
const MAX_BUFFER = 10 * 1024 * 1024; // 10 MB
|
|
58
|
+
class OryCliNotFoundError extends Error {
|
|
59
|
+
constructor() {
|
|
60
|
+
super("The 'ory' CLI binary was not found on PATH. " +
|
|
61
|
+
"Install it from https://ory.com/docs/guides/cli/installation " +
|
|
62
|
+
"or set the ORY_CLI_PATH environment variable to the binary location.");
|
|
63
|
+
this.name = "OryCliNotFoundError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.OryCliNotFoundError = OryCliNotFoundError;
|
|
67
|
+
/**
|
|
68
|
+
* Run an ory CLI command and return the result.
|
|
69
|
+
*
|
|
70
|
+
* Non-zero exit codes are not thrown — the caller inspects stdout/stderr.
|
|
71
|
+
* Only truly fatal errors (binary missing, timeout) are thrown.
|
|
72
|
+
*/
|
|
73
|
+
async function runOryCommand(args) {
|
|
74
|
+
(0, logger_js_1.log)("debug", "executing ory command", { args });
|
|
75
|
+
try {
|
|
76
|
+
const result = await execFileAsync(ORY_BINARY, args, {
|
|
77
|
+
maxBuffer: MAX_BUFFER,
|
|
78
|
+
timeout: TIMEOUT,
|
|
79
|
+
});
|
|
80
|
+
return { stdout: result.stdout, stderr: result.stderr, exitCode: 0 };
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const execErr = err;
|
|
84
|
+
if (execErr.code === "ENOENT") {
|
|
85
|
+
throw new OryCliNotFoundError();
|
|
86
|
+
}
|
|
87
|
+
// Timeout
|
|
88
|
+
if (execErr.killed || execErr.signal === "SIGTERM") {
|
|
89
|
+
return {
|
|
90
|
+
stdout: "",
|
|
91
|
+
stderr: `Command timed out after ${TIMEOUT}ms. Increase ORY_CLI_TIMEOUT if needed.`,
|
|
92
|
+
exitCode: 124,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Non-zero exit but command ran — return output for the caller to handle
|
|
96
|
+
if (execErr.stdout !== undefined || execErr.stderr !== undefined) {
|
|
97
|
+
return {
|
|
98
|
+
stdout: execErr.stdout ?? "",
|
|
99
|
+
stderr: execErr.stderr ?? "",
|
|
100
|
+
exitCode: 1,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the common args appended to every ory command.
|
|
108
|
+
* Adds --format json and optional --project / --workspace flags.
|
|
109
|
+
*/
|
|
110
|
+
function buildCommonArgs(params) {
|
|
111
|
+
const args = [];
|
|
112
|
+
if (params.format !== false) {
|
|
113
|
+
args.push("--format", "json");
|
|
114
|
+
}
|
|
115
|
+
const project = params.project ?? process.env.ORY_PROJECT;
|
|
116
|
+
const workspace = params.workspace ?? process.env.ORY_WORKSPACE;
|
|
117
|
+
if (project)
|
|
118
|
+
args.push("--project", project);
|
|
119
|
+
if (workspace)
|
|
120
|
+
args.push("--workspace", workspace);
|
|
121
|
+
return args;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Helper to format an ory command result as an MCP tool response.
|
|
125
|
+
*/
|
|
126
|
+
function formatToolResult(result) {
|
|
127
|
+
if (result.exitCode !== 0) {
|
|
128
|
+
const text = result.stderr || result.stdout || "Command failed with no output.";
|
|
129
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
130
|
+
}
|
|
131
|
+
return { content: [{ type: "text", text: result.stdout }] };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run an ory command and return a formatted MCP tool response.
|
|
135
|
+
* Catches OryCliNotFoundError and returns a user-friendly message.
|
|
136
|
+
*/
|
|
137
|
+
async function execOryTool(args) {
|
|
138
|
+
try {
|
|
139
|
+
const result = await runOryCommand(args);
|
|
140
|
+
return formatToolResult(result);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
if (err instanceof OryCliNotFoundError) {
|
|
144
|
+
return { content: [{ type: "text", text: err.message }], isError: true };
|
|
145
|
+
}
|
|
146
|
+
const message = err instanceof Error ? err.message : "Unknown error executing ory CLI.";
|
|
147
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* For CLI commands that accept either a file path or inline content,
|
|
152
|
+
* write the content to a tempfile and clean up afterwards.
|
|
153
|
+
*
|
|
154
|
+
* Returns an MCP error response if neither is provided. Pass `extension`
|
|
155
|
+
* to control the temp file suffix (e.g. ".json", ".ts").
|
|
156
|
+
*/
|
|
157
|
+
async function withFileOrContent(params, extension, fn) {
|
|
158
|
+
if (!params.file && !params.content) {
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: "Either 'content' or 'file' must be provided.",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
isError: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (params.file) {
|
|
170
|
+
return fn(params.file);
|
|
171
|
+
}
|
|
172
|
+
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "ory-mcp-"));
|
|
173
|
+
const tempFile = path.join(tmpDir, `payload${extension}`);
|
|
174
|
+
await fs.promises.writeFile(tempFile, params.content, "utf-8");
|
|
175
|
+
try {
|
|
176
|
+
return await fn(tempFile);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await fs.promises.rm(tmpDir, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Ory MCP Server — wraps the Ory CLI as an MCP tool provider.
|
|
5
|
+
*
|
|
6
|
+
* Communicates via stdio (stdin/stdout) using the Model Context Protocol.
|
|
7
|
+
* All logging goes to stderr.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
11
|
+
const server_js_1 = require("./server.js");
|
|
12
|
+
const logger_js_1 = require("./logger.js");
|
|
13
|
+
async function main() {
|
|
14
|
+
(0, logger_js_1.log)("info", "starting ory mcp server");
|
|
15
|
+
const server = (0, server_js_1.createOryMcpServer)();
|
|
16
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
(0, logger_js_1.log)("info", "ory mcp server connected via stdio");
|
|
19
|
+
}
|
|
20
|
+
main().catch((err) => {
|
|
21
|
+
(0, logger_js_1.log)("error", "fatal error", {
|
|
22
|
+
error: err instanceof Error ? err.message : String(err),
|
|
23
|
+
});
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal stderr logger for the MCP server.
|
|
4
|
+
*
|
|
5
|
+
* All output goes to stderr — stdout is reserved for the MCP protocol.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.log = log;
|
|
9
|
+
const DEBUG = process.env.ORY_MCP_DEBUG === "true";
|
|
10
|
+
function log(level, message, data) {
|
|
11
|
+
if (level === "debug" && !DEBUG)
|
|
12
|
+
return;
|
|
13
|
+
const entry = JSON.stringify({
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
level,
|
|
16
|
+
component: "ory-mcp-server",
|
|
17
|
+
message,
|
|
18
|
+
...data,
|
|
19
|
+
});
|
|
20
|
+
process.stderr.write(entry + "\n");
|
|
21
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server creation and tool registration.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the Ory CLI and Ory Network REST API and registers tool groups for
|
|
5
|
+
* identities, OAuth2, relationships, projects, configuration, workspaces,
|
|
6
|
+
* organizations, JWKs, event streams, and a generic REST API request.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function createOryMcpServer(): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP server creation and tool registration.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the Ory CLI and Ory Network REST API and registers tool groups for
|
|
6
|
+
* identities, OAuth2, relationships, projects, configuration, workspaces,
|
|
7
|
+
* organizations, JWKs, event streams, and a generic REST API request.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createOryMcpServer = createOryMcpServer;
|
|
11
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
12
|
+
const identities_js_1 = require("./tools/identities.js");
|
|
13
|
+
const oauth2_js_1 = require("./tools/oauth2.js");
|
|
14
|
+
const relationships_js_1 = require("./tools/relationships.js");
|
|
15
|
+
const projects_js_1 = require("./tools/projects.js");
|
|
16
|
+
const config_js_1 = require("./tools/config.js");
|
|
17
|
+
const workspaces_js_1 = require("./tools/workspaces.js");
|
|
18
|
+
const organizations_js_1 = require("./tools/organizations.js");
|
|
19
|
+
const jwk_js_1 = require("./tools/jwk.js");
|
|
20
|
+
const event_streams_js_1 = require("./tools/event_streams.js");
|
|
21
|
+
const api_js_1 = require("./tools/api.js");
|
|
22
|
+
// Read version from package.json at build time via a constant.
|
|
23
|
+
// This avoids dynamic require() issues in CJS/ESM interop.
|
|
24
|
+
const VERSION = "0.1.0";
|
|
25
|
+
function createOryMcpServer() {
|
|
26
|
+
const server = new mcp_js_1.McpServer({
|
|
27
|
+
name: "ory",
|
|
28
|
+
version: VERSION,
|
|
29
|
+
});
|
|
30
|
+
(0, identities_js_1.registerIdentityTools)(server);
|
|
31
|
+
(0, oauth2_js_1.registerOAuth2Tools)(server);
|
|
32
|
+
(0, relationships_js_1.registerRelationshipTools)(server);
|
|
33
|
+
(0, projects_js_1.registerProjectTools)(server);
|
|
34
|
+
(0, config_js_1.registerConfigTools)(server);
|
|
35
|
+
(0, workspaces_js_1.registerWorkspaceTools)(server);
|
|
36
|
+
(0, organizations_js_1.registerOrganizationTools)(server);
|
|
37
|
+
(0, jwk_js_1.registerJwkTools)(server);
|
|
38
|
+
(0, event_streams_js_1.registerEventStreamTools)(server);
|
|
39
|
+
(0, api_js_1.registerApiTools)(server);
|
|
40
|
+
return server;
|
|
41
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerApiTools = registerApiTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const logger_js_1 = require("../logger.js");
|
|
6
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
7
|
+
const TIMEOUT = Number(process.env.ORY_HTTP_TIMEOUT) || 30_000;
|
|
8
|
+
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5 MB
|
|
9
|
+
function resolveBaseUrl(explicit) {
|
|
10
|
+
return explicit ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL ?? null;
|
|
11
|
+
}
|
|
12
|
+
function resolveApiKey(explicit) {
|
|
13
|
+
return explicit ?? process.env.ORY_API_KEY ?? process.env.ORY_ACCESS_TOKEN;
|
|
14
|
+
}
|
|
15
|
+
function buildUrl(baseUrl, path, query) {
|
|
16
|
+
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
17
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
18
|
+
const url = new URL(`${trimmedBase}${normalizedPath}`);
|
|
19
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
20
|
+
url.searchParams.append(key, value);
|
|
21
|
+
}
|
|
22
|
+
return url.toString();
|
|
23
|
+
}
|
|
24
|
+
async function executeApiCall(params) {
|
|
25
|
+
const baseUrl = resolveBaseUrl(params.base_url);
|
|
26
|
+
if (!baseUrl) {
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: "No base URL is configured. Set the ORY_PROJECT_URL environment variable or pass 'base_url'.",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const apiKey = resolveApiKey(params.api_key);
|
|
38
|
+
const url = buildUrl(baseUrl, params.path, params.query);
|
|
39
|
+
const headers = {
|
|
40
|
+
accept: "application/json",
|
|
41
|
+
...(params.headers ?? {}),
|
|
42
|
+
};
|
|
43
|
+
if (apiKey && !headers.authorization && !headers.Authorization) {
|
|
44
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
45
|
+
}
|
|
46
|
+
if (params.body && !headers["content-type"] && !headers["Content-Type"]) {
|
|
47
|
+
headers["content-type"] = "application/json";
|
|
48
|
+
}
|
|
49
|
+
(0, logger_js_1.log)("debug", "ory api request", {
|
|
50
|
+
method: params.method,
|
|
51
|
+
url,
|
|
52
|
+
has_body: Boolean(params.body),
|
|
53
|
+
});
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT);
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
method: params.method,
|
|
59
|
+
headers,
|
|
60
|
+
body: params.body,
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
});
|
|
63
|
+
let text;
|
|
64
|
+
const reader = response.body?.getReader();
|
|
65
|
+
if (reader) {
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let total = 0;
|
|
68
|
+
while (true) {
|
|
69
|
+
const { value, done } = await reader.read();
|
|
70
|
+
if (done)
|
|
71
|
+
break;
|
|
72
|
+
if (!value)
|
|
73
|
+
continue;
|
|
74
|
+
total += value.byteLength;
|
|
75
|
+
if (total > MAX_RESPONSE_SIZE) {
|
|
76
|
+
await reader.cancel();
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Response exceeded ${MAX_RESPONSE_SIZE} bytes. Refine the request or filter the result.`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
chunks.push(value);
|
|
88
|
+
}
|
|
89
|
+
text = Buffer.concat(chunks).toString("utf-8");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
text = await response.text();
|
|
93
|
+
}
|
|
94
|
+
const summary = {
|
|
95
|
+
status: response.status,
|
|
96
|
+
statusText: response.statusText,
|
|
97
|
+
body: text,
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
102
|
+
],
|
|
103
|
+
isError: !response.ok,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err.name === "AbortError") {
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: `Request timed out after ${TIMEOUT}ms. Increase ORY_HTTP_TIMEOUT if needed.`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
isError: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text", text: `Request failed: ${message}` }],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function registerApiTools(server) {
|
|
129
|
+
server.registerTool("ory_api_request", {
|
|
130
|
+
title: "Ory REST API Request",
|
|
131
|
+
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.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
method: zod_1.z
|
|
134
|
+
.enum(HTTP_METHODS)
|
|
135
|
+
.describe("HTTP method"),
|
|
136
|
+
path: zod_1.z
|
|
137
|
+
.string()
|
|
138
|
+
.describe("Path component of the URL (e.g. '/admin/identities', '/admin/sessions')"),
|
|
139
|
+
base_url: zod_1.z
|
|
140
|
+
.string()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("Override the base URL. Defaults to ORY_PROJECT_URL or ORY_SDK_URL env var."),
|
|
143
|
+
query: zod_1.z
|
|
144
|
+
.record(zod_1.z.string(), zod_1.z.string())
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Query string parameters as a key/value map"),
|
|
147
|
+
headers: zod_1.z
|
|
148
|
+
.record(zod_1.z.string(), zod_1.z.string())
|
|
149
|
+
.optional()
|
|
150
|
+
.describe("Additional HTTP headers. 'authorization' is set automatically from ORY_API_KEY unless provided."),
|
|
151
|
+
body: zod_1.z
|
|
152
|
+
.string()
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Request body as a string. JSON content-type is added automatically when a body is provided."),
|
|
155
|
+
api_key: zod_1.z
|
|
156
|
+
.string()
|
|
157
|
+
.optional()
|
|
158
|
+
.describe("Override the bearer token. Defaults to ORY_API_KEY env var."),
|
|
159
|
+
},
|
|
160
|
+
}, async (params) => executeApiCall(params));
|
|
161
|
+
}
|