@elvix.is/sdk 0.0.0 → 0.1.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/LICENSE +34 -0
- package/README.md +189 -3
- package/dist/chunk-22IQNPXM.js +77 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +0 -0
- package/dist/mcp/bin.d.ts +1 -0
- package/dist/mcp/bin.js +27 -0
- package/dist/mcp/index.d.ts +29 -0
- package/dist/mcp/index.js +6 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.js +0 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.js +47 -0
- package/package.json +74 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 edvone
|
|
4
|
+
Aachen, Germany · edvone.dev
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
|
24
|
+
────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
TRADEMARK NOTICE
|
|
27
|
+
|
|
28
|
+
The MIT License above covers source code only. "elvix", the elvix logomark,
|
|
29
|
+
and the elvix brand chord (deep purple #5d4dff and #8e7dff) are trademarks
|
|
30
|
+
of edvone. Forks may not use the elvix name or logomark to identify
|
|
31
|
+
themselves or their products. Reasonable nominative use ("works with elvix",
|
|
32
|
+
"elvix-compatible", "integrates with elvix") is permitted.
|
|
33
|
+
|
|
34
|
+
Trademark questions: edvard@edvone.dev
|
package/README.md
CHANGED
|
@@ -1,5 +1,191 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://elvix.is/brand/elvix-icon-512.png" width="96" height="96" alt="elvix" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">@elvix.is/sdk</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Identity, kept in Europe.</strong><br/>
|
|
9
|
+
Passwordless authentication for React + Next.js. Hosted in Aachen, German legal frame.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/@elvix.is/sdk"><img src="https://img.shields.io/npm/v/@elvix.is/sdk.svg?color=5d4dff" alt="npm" /></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@elvix.is/sdk"><img src="https://img.shields.io/npm/dm/@elvix.is/sdk.svg?color=8e7dff" alt="downloads" /></a>
|
|
15
|
+
<a href="https://github.com/021is/elvix-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/021is/elvix-sdk/ci.yml?branch=main&label=CI" alt="CI" /></a>
|
|
16
|
+
<a href="https://github.com/021is/elvix-sdk/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-black.svg" alt="MIT" /></a>
|
|
17
|
+
<a href="https://elvix.is/docs"><img src="https://img.shields.io/badge/docs-elvix.is-5d4dff.svg" alt="docs" /></a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@elvix.is/sdk"><img src="https://img.shields.io/bundlephobia/minzip/@elvix.is/sdk?label=min%2Bgzip&color=black" alt="bundle size" /></a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Why elvix
|
|
24
|
+
|
|
25
|
+
Auth is the highest-leverage place to get an integration right or wrong. Roll your own and you ship an insecure copy of OAuth that future-you debugs at 2am. Use a US provider and your German users live under American legal frame. elvix is opinionated so the first answer is the safe answer, and EU-resident so the legal frame matches your customers.
|
|
26
|
+
|
|
27
|
+
- Passwordless from day one. Email OTP, passkeys, Google.
|
|
28
|
+
- Drop-in React components. One provider, one form, zero boilerplate.
|
|
29
|
+
- Server-side verify in three lines.
|
|
30
|
+
- Console-configured. Brand colors, allowed methods, redirects all live in elvix Console. The SDK reads them at runtime. No prop drilling.
|
|
31
|
+
- Agent-friendly. Ships an MCP server so Claude, Cursor, Codex, and Gemini can integrate elvix without human shepherding.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bun add @elvix.is/sdk
|
|
37
|
+
# or
|
|
38
|
+
npm install @elvix.is/sdk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// app/layout.tsx
|
|
45
|
+
import { ElvixProvider } from "@elvix.is/sdk/react";
|
|
46
|
+
|
|
47
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
48
|
+
return (
|
|
49
|
+
<html>
|
|
50
|
+
<body>
|
|
51
|
+
<ElvixProvider clientId={process.env.NEXT_PUBLIC_ELVIX_CLIENT_ID!}>
|
|
52
|
+
{children}
|
|
53
|
+
</ElvixProvider>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// app/sign-in/page.tsx
|
|
62
|
+
"use client";
|
|
63
|
+
|
|
64
|
+
import { ElvixSignIn } from "@elvix.is/sdk/react";
|
|
65
|
+
import { useRouter } from "next/navigation";
|
|
66
|
+
|
|
67
|
+
export default function SignInPage() {
|
|
68
|
+
const router = useRouter();
|
|
69
|
+
return (
|
|
70
|
+
<ElvixSignIn
|
|
71
|
+
onResult={(r) => {
|
|
72
|
+
if (r.ok) router.push(r.redirect ?? "/dashboard");
|
|
73
|
+
else console.warn(r.error, r.message);
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// app/api/protected/route.ts
|
|
82
|
+
export async function GET(request: Request) {
|
|
83
|
+
const token = request.headers.get("authorization")?.replace(/^Bearer /, "");
|
|
84
|
+
if (!token) return new Response("Unauthorized", { status: 401 });
|
|
85
|
+
|
|
86
|
+
const res = await fetch("https://elvix.is/api/v1/verify", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
Authorization: `Bearer ${process.env.ELVIX_API_KEY!}`,
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({ token }),
|
|
93
|
+
});
|
|
94
|
+
const { ok, user, roles } = await res.json();
|
|
95
|
+
if (!ok) return new Response("Unauthorized", { status: 401 });
|
|
96
|
+
return Response.json({ hello: user.id, roles });
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That is the entire integration.
|
|
101
|
+
|
|
102
|
+
## AI coding agents
|
|
103
|
+
|
|
104
|
+
elvix ships first-class agent support. Three surfaces:
|
|
105
|
+
|
|
106
|
+
1. **Discovery via [llmstxt.org](https://llmstxt.org)**
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
https://elvix.is/llms.txt index
|
|
110
|
+
https://elvix.is/llms-full.txt flat dump of every doc page
|
|
111
|
+
https://elvix.is/docs/install.md per-page Markdown twin
|
|
112
|
+
https://elvix.is/agent-prompt.md ready-to-paste system prompt
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
2. **OpenAPI for typed REST access**
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
https://elvix.is/openapi.yaml full spec
|
|
119
|
+
https://elvix.is/openapi.roles.json per-endpoint role + admin scope
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. **MCP server bundled with the SDK**
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"elvix": {
|
|
128
|
+
"command": "bunx",
|
|
129
|
+
"args": ["@elvix.is/sdk", "elvix-mcp"],
|
|
130
|
+
"env": { "ELVIX_API_KEY": "eak_..." }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Read-only by default. `--admin` opts in to mutation tools. Never logs the bearer token.
|
|
137
|
+
|
|
138
|
+
Full agent guide: <https://elvix.is/docs/agents>
|
|
139
|
+
|
|
140
|
+
## Components
|
|
141
|
+
|
|
142
|
+
Every `<Elvix*>` component the SDK ships. Drop-in React, brand chord from `<ElvixProvider>`, no prop drilling.
|
|
143
|
+
|
|
144
|
+
- Primitives: `ElvixCard`, `ElvixProvider`
|
|
145
|
+
- Sign-in: `ElvixSignIn`, `ElvixSignInButton`, `ElvixRecoverGate`
|
|
146
|
+
- Identity: `ElvixUsername`, `ElvixIdentityForm`, `ElvixAvatar`, `ElvixBanner`, `ElvixRegion`, `ElvixLanguages`
|
|
147
|
+
- Account: `ElvixAddressBook`, `ElvixLegalEntities`, `ElvixSessions`, `ElvixExport`, `ElvixDeactivate`, `ElvixLeave`
|
|
148
|
+
|
|
149
|
+
Full catalog with previews: <https://elvix.is/docs/components>
|
|
150
|
+
|
|
151
|
+
## Server helpers
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { verifyElvixToken } from "@elvix.is/sdk/server";
|
|
155
|
+
|
|
156
|
+
const result = await verifyElvixToken(token, { apiKey: process.env.ELVIX_API_KEY! });
|
|
157
|
+
if (result.ok) {
|
|
158
|
+
// result.user, result.roles, result.scopes, result.memberships
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Brand
|
|
163
|
+
|
|
164
|
+
Deep purple chord: `#5d4dff` (light) and `#8e7dff` (dark). Override per-app from the Console. Set explicit `brand` on `<ElvixProvider>` to win over the Console default.
|
|
165
|
+
|
|
166
|
+
## Security
|
|
167
|
+
|
|
168
|
+
- All requests over TLS 1.3.
|
|
169
|
+
- Session cookies `Secure; HttpOnly; SameSite=Lax`.
|
|
170
|
+
- Per-app session TTL + sliding-window renewal, owner-configurable.
|
|
171
|
+
- API keys carry per-key rate limits (60/min, 10000/day default).
|
|
172
|
+
- CSP, CORS, CSRF double-submit, allowedOrigins enforcement all live on `elvix.is`.
|
|
173
|
+
- Disclosure: <security@elvix.is>.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT. See [LICENSE](./LICENSE).
|
|
178
|
+
|
|
179
|
+
## Security
|
|
180
|
+
|
|
181
|
+
Found something? Read [SECURITY.md](./SECURITY.md). Reports go to **security@elvix.is**. Critical issues get a 4-hour response.
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). PRs welcome — we run CI on every push and require it green before merge.
|
|
186
|
+
|
|
187
|
+
## Maintained by
|
|
188
|
+
|
|
189
|
+
**[edvone](https://edvone.dev)** · Aachen, Germany · [hi@edvone.dev](mailto:hi@edvone.dev)
|
|
190
|
+
|
|
191
|
+
elvix is an edvone product.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/mcp/index.ts
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
var DEFAULT_BASE_URL = "https://elvix.is";
|
|
9
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
10
|
+
function toolName(method, path) {
|
|
11
|
+
return `${method.toLowerCase()}_${path.replace(/^\/api\//, "").replace(/[\/{}]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "")}`;
|
|
12
|
+
}
|
|
13
|
+
async function createElvixMcpServer(opts) {
|
|
14
|
+
const baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
|
|
15
|
+
const readonly = opts.readonly ?? true;
|
|
16
|
+
const manifestRes = await fetch(`${baseUrl}/openapi.roles.json`);
|
|
17
|
+
const manifest = await manifestRes.json();
|
|
18
|
+
const tools = manifest.endpoints.filter((e) => e.role === "api").filter((e) => readonly ? SAFE_METHODS.has(e.method.toUpperCase()) : true).map((e) => ({
|
|
19
|
+
name: toolName(e.method, e.path),
|
|
20
|
+
description: `${e.summary ?? `${e.method} ${e.path}`}${e.adminScope ? " (requires admin scope)" : ""}`,
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
path: { type: "string", description: "Final URL path (substitute {params})." },
|
|
25
|
+
body: { type: "object", description: "JSON request body, when applicable." },
|
|
26
|
+
query: { type: "object", description: "Query parameters." }
|
|
27
|
+
},
|
|
28
|
+
required: ["path"]
|
|
29
|
+
},
|
|
30
|
+
_meta: { method: e.method, path: e.path, adminScope: e.adminScope ?? false }
|
|
31
|
+
}));
|
|
32
|
+
const server = new Server(
|
|
33
|
+
{ name: "elvix", version: "0.1.0" },
|
|
34
|
+
{ capabilities: { tools: {} } }
|
|
35
|
+
);
|
|
36
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
|
+
tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }))
|
|
38
|
+
}));
|
|
39
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
40
|
+
const tool = tools.find((t) => t.name === req.params.name);
|
|
41
|
+
if (!tool) {
|
|
42
|
+
return { content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }], isError: true };
|
|
43
|
+
}
|
|
44
|
+
const args = req.params.arguments ?? {};
|
|
45
|
+
const url = new URL(args.path ?? tool._meta.path, baseUrl);
|
|
46
|
+
if (args.query) {
|
|
47
|
+
for (const [k, v] of Object.entries(args.query)) url.searchParams.set(k, v);
|
|
48
|
+
}
|
|
49
|
+
const init = {
|
|
50
|
+
method: tool._meta.method,
|
|
51
|
+
headers: {
|
|
52
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
53
|
+
"content-type": "application/json"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
if (args.body && !SAFE_METHODS.has(tool._meta.method.toUpperCase())) {
|
|
57
|
+
init.body = JSON.stringify(args.body);
|
|
58
|
+
}
|
|
59
|
+
const res = await fetch(url, init);
|
|
60
|
+
const text = await res.text();
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text }],
|
|
63
|
+
isError: !res.ok
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
server,
|
|
68
|
+
connectStdio: async () => {
|
|
69
|
+
const transport = new StdioServerTransport();
|
|
70
|
+
await server.connect(transport);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
createElvixMcpServer
|
|
77
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-level types shared between the React, server, and MCP layers.
|
|
3
|
+
* Mirrors the elvix.is REST envelopes — bump together with the
|
|
4
|
+
* server when they evolve.
|
|
5
|
+
*/
|
|
6
|
+
type ElvixUser = {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
avatarUrl?: string;
|
|
11
|
+
};
|
|
12
|
+
type ElvixVerifyOk = {
|
|
13
|
+
ok: true;
|
|
14
|
+
user: ElvixUser;
|
|
15
|
+
roles: string[];
|
|
16
|
+
scopes: string[];
|
|
17
|
+
memberships: string[];
|
|
18
|
+
};
|
|
19
|
+
type ElvixVerifyErr = {
|
|
20
|
+
ok: false;
|
|
21
|
+
error: "invalid_token" | "expired" | "revoked" | "membership_blocked" | "rate_limited";
|
|
22
|
+
message?: string;
|
|
23
|
+
};
|
|
24
|
+
type ElvixVerifyResult = ElvixVerifyOk | ElvixVerifyErr;
|
|
25
|
+
/**
|
|
26
|
+
* Discriminated union returned to host apps by every `<Elvix*>`
|
|
27
|
+
* mutation component's `onResult` callback. Always carries "safe to
|
|
28
|
+
* give back" data — no PII beyond what the customer already sees.
|
|
29
|
+
*/
|
|
30
|
+
type ElvixActionResult<T = unknown> = {
|
|
31
|
+
ok: true;
|
|
32
|
+
data?: T;
|
|
33
|
+
redirect?: string;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
error: string;
|
|
37
|
+
message?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type { ElvixActionResult, ElvixUser, ElvixVerifyErr, ElvixVerifyOk, ElvixVerifyResult };
|
package/dist/index.js
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/mcp/bin.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createElvixMcpServer
|
|
4
|
+
} from "../chunk-22IQNPXM.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp/bin.ts
|
|
7
|
+
async function main() {
|
|
8
|
+
const apiKey = process.env.ELVIX_API_KEY;
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
process.stderr.write("ELVIX_API_KEY environment variable is required.\n");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const admin = args.includes("--admin");
|
|
15
|
+
const baseUrl = args.find((a) => a.startsWith("--base-url="))?.split("=")[1];
|
|
16
|
+
const { connectStdio } = await createElvixMcpServer({
|
|
17
|
+
apiKey,
|
|
18
|
+
readonly: !admin,
|
|
19
|
+
baseUrl
|
|
20
|
+
});
|
|
21
|
+
await connectStdio();
|
|
22
|
+
}
|
|
23
|
+
main().catch((e) => {
|
|
24
|
+
process.stderr.write(`elvix-mcp: ${e instanceof Error ? e.message : String(e)}
|
|
25
|
+
`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Programmatic entry to the elvix MCP server. The CLI in `bin.ts`
|
|
5
|
+
* thin-wraps this so embedders can run an MCP server in-process if
|
|
6
|
+
* they want (testing, custom transports, multi-server hosts).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type ElvixMcpOptions = {
|
|
10
|
+
/** Bearer token used for every tool call. Required. */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** Override the elvix origin (testing, proxy). */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** When false, mutation tools (POST/PATCH/PUT/DELETE) are hidden
|
|
15
|
+
* from the tool list. Default true. Pass `--readonly`/`--admin`
|
|
16
|
+
* on the CLI to flip. */
|
|
17
|
+
readonly?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Build (but don't connect) the MCP server. Returns the Server
|
|
21
|
+
* instance + a `connect()` to wire stdio. Lets callers choose
|
|
22
|
+
* transport.
|
|
23
|
+
*/
|
|
24
|
+
declare function createElvixMcpServer(opts: ElvixMcpOptions): Promise<{
|
|
25
|
+
server: Server;
|
|
26
|
+
connectStdio: () => Promise<void>;
|
|
27
|
+
}>;
|
|
28
|
+
|
|
29
|
+
export { type ElvixMcpOptions, createElvixMcpServer };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ElvixActionResult, ElvixUser, ElvixVerifyErr, ElvixVerifyOk, ElvixVerifyResult } from './index.js';
|
package/dist/react.js
ADDED
|
File without changes
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ElvixVerifyResult } from './index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Server-side helpers for verifying elvix-issued session tokens.
|
|
5
|
+
* Customer backends call `verifyElvixToken` with the request's
|
|
6
|
+
* Authorization header. Bearer-token auth, no cookies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type VerifyOptions = {
|
|
10
|
+
/** Application API key (Console → Credentials). */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** Override the elvix origin for testing / proxy setups. */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** Per-request timeout in ms. Default 5000. */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Exchange an end-user session token for the verified user envelope
|
|
19
|
+
* (roles + scopes + memberships). Hit the `/api/v1/verify` endpoint
|
|
20
|
+
* with the customer's Application API key.
|
|
21
|
+
*
|
|
22
|
+
* Returns a discriminated union — never throws on auth failure.
|
|
23
|
+
* Throws only on infra failure (network, timeout, malformed JSON).
|
|
24
|
+
*/
|
|
25
|
+
declare function verifyElvixToken(token: string, opts: VerifyOptions): Promise<ElvixVerifyResult>;
|
|
26
|
+
|
|
27
|
+
export { type VerifyOptions, verifyElvixToken };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://elvix.is";
|
|
3
|
+
async function verifyElvixToken(token, opts) {
|
|
4
|
+
const url = `${opts.baseUrl ?? DEFAULT_BASE_URL}/api/v1/verify`;
|
|
5
|
+
const ctrl = new AbortController();
|
|
6
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 5e3);
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(url, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
"content-type": "application/json",
|
|
12
|
+
authorization: `Bearer ${opts.apiKey}`
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({ token }),
|
|
15
|
+
signal: ctrl.signal
|
|
16
|
+
});
|
|
17
|
+
const body = await res.json();
|
|
18
|
+
if (!res.ok || !body.success || !body.data) {
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: pickError(body.errorMessage, res.status),
|
|
22
|
+
message: body.errorMessage
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
user: body.data.user,
|
|
28
|
+
roles: body.data.roles,
|
|
29
|
+
scopes: body.data.scopes,
|
|
30
|
+
memberships: body.data.memberships
|
|
31
|
+
};
|
|
32
|
+
} finally {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function pickError(raw, status) {
|
|
37
|
+
if (raw === "expired" || raw === "revoked" || raw === "membership_blocked" || raw === "rate_limited") {
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
if (status === 401) return "invalid_token";
|
|
41
|
+
if (status === 403) return "membership_blocked";
|
|
42
|
+
if (status === 429) return "rate_limited";
|
|
43
|
+
return "invalid_token";
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
verifyElvixToken
|
|
47
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,79 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elvix.is/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "elvix
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Official elvix SDK. Drop-in React components, server helpers, and an MCP server so AI coding agents integrate elvix on the first try.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://elvix.is",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"author": "edvone <hi@edvone.dev> (https://edvone.dev)",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/021is/elvix-sdk.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/021is/elvix-sdk/issues",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"bin": {
|
|
17
|
+
"elvix-mcp": "./dist/mcp/bin.js"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./react": {
|
|
25
|
+
"types": "./dist/react.d.ts",
|
|
26
|
+
"import": "./dist/react.js"
|
|
27
|
+
},
|
|
28
|
+
"./server": {
|
|
29
|
+
"types": "./dist/server.d.ts",
|
|
30
|
+
"import": "./dist/server.js"
|
|
31
|
+
},
|
|
32
|
+
"./types": {
|
|
33
|
+
"types": "./dist/types.d.ts",
|
|
34
|
+
"import": "./dist/types.js"
|
|
35
|
+
},
|
|
36
|
+
"./mcp": {
|
|
37
|
+
"types": "./dist/mcp/index.d.ts",
|
|
38
|
+
"import": "./dist/mcp/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup src/index.ts src/react.ts src/server.ts src/types.ts src/mcp/index.ts src/mcp/bin.ts --format esm --dts --clean --external react --external next",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=18",
|
|
49
|
+
"next": ">=15"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"next": { "optional": true }
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^22.7.5",
|
|
59
|
+
"tsup": "^8.3.0",
|
|
60
|
+
"typescript": "^5.6.3",
|
|
61
|
+
"vitest": "^2.1.2"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"keywords": [
|
|
67
|
+
"elvix",
|
|
68
|
+
"authentication",
|
|
69
|
+
"auth",
|
|
70
|
+
"passkey",
|
|
71
|
+
"otp",
|
|
72
|
+
"google-oauth",
|
|
73
|
+
"react",
|
|
74
|
+
"nextjs",
|
|
75
|
+
"mcp",
|
|
76
|
+
"model-context-protocol",
|
|
77
|
+
"ai-agents"
|
|
78
|
+
]
|
|
9
79
|
}
|