@plitzi/sdk-server 0.32.13 → 0.32.15
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/CHANGELOG.md +20 -0
- package/dist/core/services/oauth.js +25 -14
- package/dist/modules/mcp/server.js +1 -1
- package/dist/modules/oauth/authorize.js +20 -4
- package/dist/modules/oauth/consentPage.js +10 -3
- package/dist/modules/oauth/records.js +10 -1
- package/dist/modules/oauth/register.js +15 -8
- package/dist/modules/oauth/respond.js +4 -4
- package/dist/modules/oauth/token.js +9 -7
- package/dist/src/modules/mcp/e2e/oauthConnector.test.d.ts +1 -0
- package/dist/src/modules/oauth/records.d.ts +15 -4
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @plitzi/sdk-server
|
|
2
2
|
|
|
3
|
+
## 0.32.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- v0.32.15
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @plitzi/plitzi-sdk@0.32.15
|
|
10
|
+
- @plitzi/sdk-schema@0.32.15
|
|
11
|
+
- @plitzi/sdk-shared@0.32.15
|
|
12
|
+
|
|
13
|
+
## 0.32.14
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- v0.32.14
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @plitzi/plitzi-sdk@0.32.14
|
|
20
|
+
- @plitzi/sdk-schema@0.32.14
|
|
21
|
+
- @plitzi/sdk-shared@0.32.14
|
|
22
|
+
|
|
3
23
|
## 0.32.13
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -69,20 +69,30 @@ var oauthStage = async (ctx) => {
|
|
|
69
69
|
sendErrorJson(res, 405, "invalid_request", `${method} is not allowed on ${path}.`);
|
|
70
70
|
return true;
|
|
71
71
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
var CHALLENGE_DESCRIPTIONS = {
|
|
73
|
+
"no-credential": "Authorization is required to use this server.",
|
|
74
|
+
"unknown-credential": "The access token is invalid, expired or revoked.",
|
|
75
|
+
"store-unreachable": "The access token could not be verified right now.",
|
|
76
|
+
"adapter-failed": "The access token could not be verified right now."
|
|
77
|
+
};
|
|
78
|
+
var challengeReason = async (oauth, ctx, token) => {
|
|
79
|
+
if (!token) return "no-credential";
|
|
76
80
|
try {
|
|
77
|
-
|
|
81
|
+
const record = await getAccess(oauth.adapters.store, token);
|
|
82
|
+
if (record) {
|
|
83
|
+
const credential = record.credential ?? token;
|
|
84
|
+
ctx.req.headers["x-access-token"] = credential;
|
|
85
|
+
ctx.req.headers.authorization = `Bearer ${credential}`;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
} catch {
|
|
79
|
-
return
|
|
89
|
+
return "store-unreachable";
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return await ctx.config.adapters.getSpaceId?.(ctx.req) === void 0 ? "unknown-credential" : void 0;
|
|
93
|
+
} catch {
|
|
94
|
+
return "adapter-failed";
|
|
80
95
|
}
|
|
81
|
-
};
|
|
82
|
-
var isAuthorized = async (oauth, ctx, token) => {
|
|
83
|
-
if (await verified(async () => await getAccess(oauth.adapters.store, token) !== void 0)) return true;
|
|
84
|
-
const { adapters } = ctx.config;
|
|
85
|
-
return verified(async () => await adapters.getSpaceId?.(ctx.req) !== void 0);
|
|
86
96
|
};
|
|
87
97
|
/** The protected-resource half of OAuth: an MCP call that presents no bearer this server can verify is refused
|
|
88
98
|
* with RFC 6750's challenge instead of being served the anonymous surface. That 401 is the whole handshake — it
|
|
@@ -96,9 +106,10 @@ var isAuthorized = async (oauth, ctx, token) => {
|
|
|
96
106
|
var oauthGuardStage = async (ctx) => {
|
|
97
107
|
const { oauth } = ctx.config;
|
|
98
108
|
if (!oauth || ctx.req.method !== "POST") return false;
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
109
|
+
const reason = await challengeReason(oauth, ctx, bearerOf(ctx.req));
|
|
110
|
+
if (!reason) return false;
|
|
111
|
+
ctx.operation = `oauth-challenge:${reason}`;
|
|
112
|
+
sendChallenge(oauth, ctx.req, ctx.res, CHALLENGE_DESCRIPTIONS[reason]);
|
|
102
113
|
return true;
|
|
103
114
|
};
|
|
104
115
|
//#endregion
|
|
@@ -43,7 +43,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) =>
|
|
|
43
43
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
44
44
|
const server = new McpServer({
|
|
45
45
|
name: "plitzi-mcp",
|
|
46
|
-
version: "0.32.
|
|
46
|
+
version: "0.32.15"
|
|
47
47
|
}, { instructions: serverInstructions });
|
|
48
48
|
registerResources(server, getSpace, MCP_ENV, log);
|
|
49
49
|
registerApps(server);
|
|
@@ -6,6 +6,15 @@ import { dropPending, getClient, getPending, putCode, putPending } from "./recor
|
|
|
6
6
|
import { redirectWithCode, redirectWithError, sendErrorPage, sendHtml } from "./respond.js";
|
|
7
7
|
//#region src/modules/oauth/authorize.ts
|
|
8
8
|
var DEFAULT_CODE_TTL_SECONDS = 60;
|
|
9
|
+
var DEFAULT_GUEST_LABEL = "Continue without an account";
|
|
10
|
+
var DEFAULT_GUEST_USER = {
|
|
11
|
+
id: "guest",
|
|
12
|
+
label: "Guest"
|
|
13
|
+
};
|
|
14
|
+
var guestView = (guest) => ({
|
|
15
|
+
label: guest.label ?? DEFAULT_GUEST_LABEL,
|
|
16
|
+
description: guest.target.description
|
|
17
|
+
});
|
|
9
18
|
var hiddenFieldsFor = (request, pendingId) => {
|
|
10
19
|
const hidden = {
|
|
11
20
|
client_id: request.clientId,
|
|
@@ -57,8 +66,8 @@ var resolveRequest = async (config, res, params) => {
|
|
|
57
66
|
};
|
|
58
67
|
/** Consent granted: mint the bearer now, park it behind a one-shot code and send the browser back. Minting here
|
|
59
68
|
* rather than at redemption keeps a failure the user can act on — no space, revoked access — on this screen. */
|
|
60
|
-
var completeGrant = async (config, res, request,
|
|
61
|
-
const issued = await config.adapters.issueToken(
|
|
69
|
+
var completeGrant = async (config, res, request, user, target) => {
|
|
70
|
+
const issued = await config.adapters.issueToken(user, target);
|
|
62
71
|
if (!issued) {
|
|
63
72
|
redirectWithError(res, request.redirectUri, "access_denied", "The account may not grant access to this resource.", request.state);
|
|
64
73
|
return;
|
|
@@ -71,7 +80,7 @@ var completeGrant = async (config, res, request, pending, target) => {
|
|
|
71
80
|
token: issued.token,
|
|
72
81
|
expiresInSeconds: issued.expiresInSeconds,
|
|
73
82
|
scope: request.scope,
|
|
74
|
-
user
|
|
83
|
+
user,
|
|
75
84
|
target
|
|
76
85
|
}, config.codeTtlSeconds ?? DEFAULT_CODE_TTL_SECONDS);
|
|
77
86
|
redirectWithCode(res, request.redirectUri, code, request.state);
|
|
@@ -85,6 +94,7 @@ var handleAuthorizeStart = async (config, res, params) => {
|
|
|
85
94
|
action: AUTHORIZE_PATH,
|
|
86
95
|
hidden: hiddenFieldsFor(request),
|
|
87
96
|
targets: [],
|
|
97
|
+
guest: config.guest ? guestView(config.guest) : void 0,
|
|
88
98
|
branding: config.branding ?? {}
|
|
89
99
|
});
|
|
90
100
|
};
|
|
@@ -115,7 +125,12 @@ var handleAuthorizeSubmit = async (config, res, params) => {
|
|
|
115
125
|
return;
|
|
116
126
|
}
|
|
117
127
|
await dropPending(config.adapters.store, pendingId);
|
|
118
|
-
await completeGrant(config, res, request, pending, chosen);
|
|
128
|
+
await completeGrant(config, res, request, pending.user, chosen);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const { guest } = config;
|
|
132
|
+
if (guest && optionalField(params, "guest")) {
|
|
133
|
+
await completeGrant(config, res, request, guest.user ?? DEFAULT_GUEST_USER, guest.target);
|
|
119
134
|
return;
|
|
120
135
|
}
|
|
121
136
|
const user = await config.adapters.authenticate({
|
|
@@ -128,6 +143,7 @@ var handleAuthorizeSubmit = async (config, res, params) => {
|
|
|
128
143
|
action: AUTHORIZE_PATH,
|
|
129
144
|
hidden: hiddenFieldsFor(request),
|
|
130
145
|
targets: [],
|
|
146
|
+
guest: guest ? guestView(guest) : void 0,
|
|
131
147
|
error: "Those credentials did not match an account.",
|
|
132
148
|
branding: config.branding ?? {}
|
|
133
149
|
});
|
|
@@ -35,16 +35,23 @@ var STYLES = `
|
|
|
35
35
|
ul.targets span { color: var(--muted); font-size: 13px; }
|
|
36
36
|
button { width: 100%; padding: 11px 16px; border: 0; border-radius: 8px; background: var(--accent);
|
|
37
37
|
color: #fff; font: inherit; font-weight: 500; cursor: pointer; }
|
|
38
|
+
button.guest { margin-top: 10px; background: transparent; color: var(--accent);
|
|
39
|
+
border: 1px solid var(--line); }
|
|
38
40
|
p.error { margin: 0 0 16px; padding: 10px 12px; border-radius: 8px; color: var(--danger);
|
|
39
41
|
border: 1px solid currentColor; font-size: 14px; }
|
|
42
|
+
p.note { margin: 10px 0 0; color: var(--muted); font-size: 13px; text-align: center; }
|
|
40
43
|
`;
|
|
41
44
|
var hiddenFields = (hidden) => Object.entries(hidden).map(([name, value]) => `<input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`).join("\n ");
|
|
42
|
-
var
|
|
45
|
+
var guestButton = (guest) => {
|
|
46
|
+
const note = guest.description ? `\n <p class="note">${escapeHtml(guest.description)}</p>` : "";
|
|
47
|
+
return `\n <button class="guest" type="submit" name="guest" value="1" formnovalidate>${escapeHtml(guest.label)}</button>${note}`;
|
|
48
|
+
};
|
|
49
|
+
var credentialsFields = (view) => `<label for="username">Email or username</label>
|
|
43
50
|
<input id="username" name="username" type="text" autocomplete="username" autocapitalize="none"
|
|
44
51
|
spellcheck="false" required autofocus>
|
|
45
52
|
<label for="password">Password</label>
|
|
46
53
|
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
|
47
|
-
<button type="submit">Sign in</button
|
|
54
|
+
<button type="submit">Sign in</button>${view.guest ? guestButton(view.guest) : ""}`;
|
|
48
55
|
var targetFields = (view) => {
|
|
49
56
|
return `<ul class="targets">
|
|
50
57
|
${view.targets.map((target, index) => {
|
|
@@ -82,7 +89,7 @@ var renderConsentPage = (view) => {
|
|
|
82
89
|
${error}
|
|
83
90
|
<form method="post" action="${escapeHtml(view.action)}">
|
|
84
91
|
${hiddenFields(view.hidden)}
|
|
85
|
-
${view.step === "credentials" ? credentialsFields() : targetFields(view)}
|
|
92
|
+
${view.step === "credentials" ? credentialsFields(view) : targetFields(view)}
|
|
86
93
|
</form>
|
|
87
94
|
</main>
|
|
88
95
|
</body>
|
|
@@ -16,6 +16,15 @@ var writeJson = async (store, kind, id, value, ttlSeconds) => {
|
|
|
16
16
|
};
|
|
17
17
|
var putClient = (store, client) => writeJson(store, "client", client.clientId, client, CLIENT_TTL_SECONDS);
|
|
18
18
|
var getClient = (store, clientId) => readJson(store, "client", clientId);
|
|
19
|
+
/** The id already handed out for an identical registration. Keyed by what the client asked to be registered AS,
|
|
20
|
+
* so the same request always resolves to the same client — see handleRegister for why that matters. */
|
|
21
|
+
var getClientByFingerprint = async (store, fingerprint) => {
|
|
22
|
+
const clientId = await store.get(keyOf("clientfp", fingerprint));
|
|
23
|
+
return clientId ? getClient(store, clientId) : void 0;
|
|
24
|
+
};
|
|
25
|
+
var putClientFingerprint = async (store, fingerprint, clientId) => {
|
|
26
|
+
await store.put(keyOf("clientfp", fingerprint), clientId, CLIENT_TTL_SECONDS);
|
|
27
|
+
};
|
|
19
28
|
var putPending = (store, id, pending) => writeJson(store, "pending", id, pending, PENDING_TTL_SECONDS);
|
|
20
29
|
var getPending = (store, id) => readJson(store, "pending", id);
|
|
21
30
|
var dropPending = async (store, id) => {
|
|
@@ -34,4 +43,4 @@ var dropRefresh = async (store, token) => {
|
|
|
34
43
|
var putAccess = (store, token, record, ttlSeconds) => writeJson(store, "access", token, record, ttlSeconds);
|
|
35
44
|
var getAccess = (store, token) => readJson(store, "access", token);
|
|
36
45
|
//#endregion
|
|
37
|
-
export { dropCode, dropPending, dropRefresh, getAccess, getClient, getCode, getPending, getRefresh, putAccess, putClient, putCode, putPending, putRefresh };
|
|
46
|
+
export { dropCode, dropPending, dropRefresh, getAccess, getClient, getClientByFingerprint, getCode, getPending, getRefresh, putAccess, putClient, putClientFingerprint, putCode, putPending, putRefresh };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomId } from "./pkce.js";
|
|
2
|
-
import { putClient } from "./records.js";
|
|
2
|
+
import { getClientByFingerprint, putClient, putClientFingerprint } from "./records.js";
|
|
3
3
|
import { sendErrorJson, sendJson } from "./respond.js";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
//#region src/modules/oauth/register.ts
|
|
5
6
|
var isUsableRedirectUri = (value) => {
|
|
6
7
|
let url;
|
|
@@ -13,6 +14,8 @@ var isUsableRedirectUri = (value) => {
|
|
|
13
14
|
return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
14
15
|
};
|
|
15
16
|
var stringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
17
|
+
var nowSeconds = () => Math.floor(Date.now() / 1e3);
|
|
18
|
+
var fingerprintOf = (clientName, redirectUris) => createHash("sha256").update(JSON.stringify([clientName, [...redirectUris].sort()])).digest("base64url");
|
|
16
19
|
/** RFC 7591 dynamic client registration. A remote host has no way to be configured into this server ahead of
|
|
17
20
|
* time, so it registers itself on first connect; the record it gets back is only ever used to pin the redirect
|
|
18
21
|
* target, since a public client authenticates with PKCE rather than with credentials. */
|
|
@@ -28,15 +31,19 @@ var handleRegister = async (config, res, body) => {
|
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
33
|
const clientName = typeof metadata["client_name"] === "string" ? metadata["client_name"] : "MCP client";
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const { store } = config.adapters;
|
|
35
|
+
const fingerprint = fingerprintOf(clientName, redirectUris);
|
|
36
|
+
const client = await getClientByFingerprint(store, fingerprint) ?? {
|
|
37
|
+
clientId: randomId(),
|
|
34
38
|
clientName,
|
|
35
|
-
redirectUris
|
|
36
|
-
|
|
39
|
+
redirectUris,
|
|
40
|
+
issuedAt: nowSeconds()
|
|
41
|
+
};
|
|
42
|
+
await putClient(store, client);
|
|
43
|
+
await putClientFingerprint(store, fingerprint, client.clientId);
|
|
37
44
|
sendJson(res, 201, {
|
|
38
|
-
client_id: clientId,
|
|
39
|
-
client_id_issued_at:
|
|
45
|
+
client_id: client.clientId,
|
|
46
|
+
client_id_issued_at: client.issuedAt,
|
|
40
47
|
client_name: clientName,
|
|
41
48
|
redirect_uris: redirectUris,
|
|
42
49
|
grant_types: config.refreshTtlSeconds === 0 ? ["authorization_code"] : ["authorization_code", "refresh_token"],
|
|
@@ -3,13 +3,13 @@ import { renderErrorPage } from "./consentPage.js";
|
|
|
3
3
|
var sendJson = (res, status, body) => {
|
|
4
4
|
res.setStatus(status);
|
|
5
5
|
res.setHeader("Content-Type", "application/json");
|
|
6
|
-
res.setHeader("Cache-Control", "no-store");
|
|
6
|
+
res.setHeader("Cache-Control", "no-store, no-transform");
|
|
7
7
|
res.send(JSON.stringify(body));
|
|
8
8
|
};
|
|
9
9
|
var sendHtml = (res, status, html) => {
|
|
10
10
|
res.setStatus(status);
|
|
11
11
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
12
|
-
res.setHeader("Cache-Control", "no-store");
|
|
12
|
+
res.setHeader("Cache-Control", "no-store, no-transform");
|
|
13
13
|
res.send(html);
|
|
14
14
|
};
|
|
15
15
|
var sendErrorJson = (res, status, error, description) => sendJson(res, status, {
|
|
@@ -28,7 +28,7 @@ var redirectWithError = (res, redirectUri, error, description, state) => {
|
|
|
28
28
|
if (state !== void 0) url.searchParams.set("state", state);
|
|
29
29
|
res.setStatus(302);
|
|
30
30
|
res.setHeader("Location", url.toString());
|
|
31
|
-
res.setHeader("Cache-Control", "no-store");
|
|
31
|
+
res.setHeader("Cache-Control", "no-store, no-transform");
|
|
32
32
|
res.end();
|
|
33
33
|
};
|
|
34
34
|
var redirectWithCode = (res, redirectUri, code, state) => {
|
|
@@ -37,7 +37,7 @@ var redirectWithCode = (res, redirectUri, code, state) => {
|
|
|
37
37
|
if (state !== void 0) url.searchParams.set("state", state);
|
|
38
38
|
res.setStatus(302);
|
|
39
39
|
res.setHeader("Location", url.toString());
|
|
40
|
-
res.setHeader("Cache-Control", "no-store");
|
|
40
|
+
res.setHeader("Cache-Control", "no-store, no-transform");
|
|
41
41
|
res.end();
|
|
42
42
|
};
|
|
43
43
|
//#endregion
|
|
@@ -10,18 +10,20 @@ var refreshTtlOf = (config) => config.refreshTtlSeconds ?? DEFAULT_REFRESH_TTL_S
|
|
|
10
10
|
var scopeOf = (config, requested) => requested ?? scopesOf(config).join(" ");
|
|
11
11
|
/** The token response, plus a rotated refresh grant when refresh is enabled. Rotation is unconditional: a refresh
|
|
12
12
|
* token is a long-lived credential, so the one just used never stays valid. */
|
|
13
|
-
var sendTokens = async (config, res,
|
|
13
|
+
var sendTokens = async (config, res, credential, expiresInSeconds, grant) => {
|
|
14
14
|
const ttl = refreshTtlOf(config);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
scope: scopeOf(config, grant.scope)
|
|
19
|
-
};
|
|
20
|
-
await putAccess(config.adapters.store, token, {
|
|
15
|
+
const bearer = randomId();
|
|
16
|
+
await putAccess(config.adapters.store, bearer, {
|
|
17
|
+
credential,
|
|
21
18
|
clientId: grant.clientId,
|
|
22
19
|
user: grant.user,
|
|
23
20
|
target: grant.target
|
|
24
21
|
}, Math.max(expiresInSeconds ?? DEFAULT_ACCESS_TTL_SECONDS, 1));
|
|
22
|
+
const body = {
|
|
23
|
+
access_token: bearer,
|
|
24
|
+
token_type: "Bearer",
|
|
25
|
+
scope: scopeOf(config, grant.scope)
|
|
26
|
+
};
|
|
25
27
|
if (expiresInSeconds !== void 0) body["expires_in"] = expiresInSeconds;
|
|
26
28
|
if (ttl > 0) {
|
|
27
29
|
const refreshToken = randomId();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -5,6 +5,8 @@ export type ClientRecord = {
|
|
|
5
5
|
clientId: string;
|
|
6
6
|
clientName: string;
|
|
7
7
|
redirectUris: string[];
|
|
8
|
+
/** When this registration was first handed out, so re-registering the same client keeps reporting its real age. */
|
|
9
|
+
issuedAt: number;
|
|
8
10
|
};
|
|
9
11
|
/** The authorization request, parked while the user logs in. `challenge` binds the eventual code to the client
|
|
10
12
|
* that started the flow. */
|
|
@@ -36,17 +38,26 @@ export type RefreshRecord = {
|
|
|
36
38
|
user: OAuthUser;
|
|
37
39
|
target: OAuthGrantTarget;
|
|
38
40
|
};
|
|
39
|
-
/** A bearer this server handed out
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the
|
|
41
|
+
/** A bearer this server handed out. The token a client holds is an opaque handle minted here, and this record is
|
|
42
|
+
* what it stands for — including `credential`, the thing the deployment's own adapters understand, which never
|
|
43
|
+
* leaves the server: a platform token is usually good against more than the MCP endpoint, and a remote host has
|
|
44
|
+
* no business holding one. The record expires with the bearer, which is what turns an expired one into the 401 a
|
|
45
|
+
* host answers by refreshing; dropping it early revokes the bearer outright. */
|
|
43
46
|
export type AccessRecord = {
|
|
47
|
+
/** What {@link OAuthAdapters.issueToken} returned — swapped back onto the request once the bearer checks out.
|
|
48
|
+
* Absent only in a record written before the bearer and the credential were separate things, where the bearer
|
|
49
|
+
* WAS the credential: a live connector must survive that upgrade, so the reader falls back to the token itself. */
|
|
50
|
+
credential?: string;
|
|
44
51
|
clientId: string;
|
|
45
52
|
user: OAuthUser;
|
|
46
53
|
target: OAuthGrantTarget;
|
|
47
54
|
};
|
|
48
55
|
export declare const putClient: (store: OAuthStore, client: ClientRecord) => Promise<void>;
|
|
49
56
|
export declare const getClient: (store: OAuthStore, clientId: string) => Promise<ClientRecord | undefined>;
|
|
57
|
+
/** The id already handed out for an identical registration. Keyed by what the client asked to be registered AS,
|
|
58
|
+
* so the same request always resolves to the same client — see handleRegister for why that matters. */
|
|
59
|
+
export declare const getClientByFingerprint: (store: OAuthStore, fingerprint: string) => Promise<ClientRecord | undefined>;
|
|
60
|
+
export declare const putClientFingerprint: (store: OAuthStore, fingerprint: string, clientId: string) => Promise<void>;
|
|
50
61
|
export declare const putPending: (store: OAuthStore, id: string, pending: PendingRecord) => Promise<void>;
|
|
51
62
|
export declare const getPending: (store: OAuthStore, id: string) => Promise<PendingRecord | undefined>;
|
|
52
63
|
export declare const dropPending: (store: OAuthStore, id: string) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-server",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.15",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
|
-
"@plitzi/plitzi-sdk": "0.32.
|
|
33
|
-
"@plitzi/sdk-schema": "0.32.
|
|
34
|
-
"@plitzi/sdk-shared": "0.32.
|
|
32
|
+
"@plitzi/plitzi-sdk": "0.32.15",
|
|
33
|
+
"@plitzi/sdk-schema": "0.32.15",
|
|
34
|
+
"@plitzi/sdk-shared": "0.32.15",
|
|
35
35
|
"ejs": "^6.0.1",
|
|
36
36
|
"esbuild": "^0.28.1",
|
|
37
37
|
"zod": "^4.4.3"
|