@remit/mcp-server 0.0.0-stage → 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/dist/app-entry.d.ts +10 -0
- package/dist/app-entry.js +9 -0
- package/dist/app.d.ts +11 -0
- package/dist/app.js +261 -0
- package/dist/auth/authorization-server.d.ts +6835 -0
- package/dist/auth/authorization-server.js +592 -0
- package/dist/auth/database.d.ts +7 -0
- package/dist/auth/database.js +33 -0
- package/dist/auth/node-auth.d.ts +3 -0
- package/dist/auth/node-auth.js +17 -0
- package/dist/auth/remit-password.d.ts +8 -0
- package/dist/auth/remit-password.js +18 -0
- package/dist/auth/scopes.d.ts +6 -0
- package/dist/auth/scopes.js +13 -0
- package/dist/auth/token-verifier.d.ts +11 -0
- package/dist/auth/token-verifier.js +1 -0
- package/dist/cli/device-login.d.ts +16 -0
- package/dist/cli/device-login.js +111 -0
- package/dist/cli/mcp-login.d.ts +1 -0
- package/dist/cli/mcp-login.js +21 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +41 -0
- package/dist/deps.d.ts +33 -0
- package/dist/deps.js +14 -0
- package/dist/filter-tools.d.ts +3 -0
- package/dist/filter-tools.js +155 -0
- package/dist/folder-tools.d.ts +3 -0
- package/dist/folder-tools.js +125 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +53 -0
- package/dist/pages/assets.d.ts +8 -0
- package/dist/pages/assets.js +14 -0
- package/dist/pages/geist-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/pages/geist-cyrillic-wght-normal.woff2 +0 -0
- package/dist/pages/geist-latin-ext-wght-normal.woff2 +0 -0
- package/dist/pages/geist-latin-wght-normal.woff2 +0 -0
- package/dist/pages/geist-vietnamese-wght-normal.woff2 +0 -0
- package/dist/pages/hanken-grotesk-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/pages/hanken-grotesk-latin-ext-wght-normal.woff2 +0 -0
- package/dist/pages/hanken-grotesk-latin-wght-normal.woff2 +0 -0
- package/dist/pages/hanken-grotesk-vietnamese-wght-normal.woff2 +0 -0
- package/dist/pages/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/pages/inter-cyrillic-wght-normal.woff2 +0 -0
- package/dist/pages/inter-greek-ext-wght-normal.woff2 +0 -0
- package/dist/pages/inter-greek-wght-normal.woff2 +0 -0
- package/dist/pages/inter-latin-ext-wght-normal.woff2 +0 -0
- package/dist/pages/inter-latin-wght-normal.woff2 +0 -0
- package/dist/pages/inter-vietnamese-wght-normal.woff2 +0 -0
- package/dist/pages/load-assets.d.ts +2 -0
- package/dist/pages/load-assets.js +23 -0
- package/dist/pages/pages.css +1 -0
- package/dist/pages/render.d.ts +6 -0
- package/dist/pages/render.js +34 -0
- package/dist/pages/views.d.ts +11 -0
- package/dist/pages/views.js +20 -0
- package/dist/read-tools.d.ts +4 -0
- package/dist/read-tools.js +205 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +32 -0
- package/dist/tool-result.d.ts +5 -0
- package/dist/tool-result.js +16 -0
- package/package.json +66 -5
- package/README.md +0 -3
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createMcpApp, type McpAppConfig } from "./app.js";
|
|
2
|
+
export { AUTH_BASE_PATH, CONNECTED_APPS_API_PATH, CONNECTED_APPS_PATH, CONSENT_PATH, createBetterAuthTokenVerifier, createMcpAuth, DEVICE_APPROVE_PATH, DEVICE_CODE_PATH, DEVICE_DENY_PATH, DEVICE_PATH, JWKS_PATH, LOGIN_PATH, MCP_PATH, type McpAuth, type McpAuthConfig, type McpAuthRoutes, type McpAuthServer, type McpBrowserAuth, type McpBrowserRoutes, type McpIdentity, type McpSessionState, type McpSignInResult, type McpSubject, mcpResource, OAUTH2_PATH_PREFIX, REVOKE_CLIENT_API_PATH, SIGN_IN_PATH, type SignIn, } from "./auth/authorization-server.js";
|
|
3
|
+
export { describeScopes, MCP_READ_SCOPE, MCP_RULES_SCOPE, MCP_SCOPES, OFFLINE_ACCESS_SCOPE, } from "./auth/scopes.js";
|
|
4
|
+
export type { TokenVerification, TokenVerifier, } from "./auth/token-verifier.js";
|
|
5
|
+
export { claudeMcpAddCommand, type DeviceLoginIo, type DeviceLoginResult, deviceLogin, } from "./cli/device-login.js";
|
|
6
|
+
export { type FilterToolDeps, type FolderToolDeps, type McpDeps, type McpDepsExtras, mcpDepsOf, } from "./deps.js";
|
|
7
|
+
export { STYLESHEET_PATH } from "./pages/assets.js";
|
|
8
|
+
export { errorPage, pageError, renderPage } from "./pages/render.js";
|
|
9
|
+
export { type ConnectedAppGrant, connectedAppsView, consentView, deviceView, loginView, } from "./pages/views.js";
|
|
10
|
+
export { createMcpHandler, type McpPrincipal, type McpRequestHandler, } from "./server.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createMcpApp } from "./app.js";
|
|
2
|
+
export { AUTH_BASE_PATH, CONNECTED_APPS_API_PATH, CONNECTED_APPS_PATH, CONSENT_PATH, createBetterAuthTokenVerifier, createMcpAuth, DEVICE_APPROVE_PATH, DEVICE_CODE_PATH, DEVICE_DENY_PATH, DEVICE_PATH, JWKS_PATH, LOGIN_PATH, MCP_PATH, mcpResource, OAUTH2_PATH_PREFIX, REVOKE_CLIENT_API_PATH, SIGN_IN_PATH, } from "./auth/authorization-server.js";
|
|
3
|
+
export { describeScopes, MCP_READ_SCOPE, MCP_RULES_SCOPE, MCP_SCOPES, OFFLINE_ACCESS_SCOPE, } from "./auth/scopes.js";
|
|
4
|
+
export { claudeMcpAddCommand, deviceLogin, } from "./cli/device-login.js";
|
|
5
|
+
export { mcpDepsOf, } from "./deps.js";
|
|
6
|
+
export { STYLESHEET_PATH } from "./pages/assets.js";
|
|
7
|
+
export { errorPage, pageError, renderPage } from "./pages/render.js";
|
|
8
|
+
export { connectedAppsView, consentView, deviceView, loginView, } from "./pages/views.js";
|
|
9
|
+
export { createMcpHandler, } from "./server.js";
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type McpAuthRoutes } from "./auth/authorization-server.js";
|
|
2
|
+
import type { TokenVerifier } from "./auth/token-verifier.js";
|
|
3
|
+
import type { McpDeps } from "./deps.js";
|
|
4
|
+
import { type McpRequestHandler } from "./server.js";
|
|
5
|
+
export interface McpAppConfig {
|
|
6
|
+
origin: string;
|
|
7
|
+
authRoutes: McpAuthRoutes;
|
|
8
|
+
verifier: TokenVerifier;
|
|
9
|
+
deps: McpDeps;
|
|
10
|
+
}
|
|
11
|
+
export declare const createMcpApp: ({ origin, authRoutes, verifier, deps, }: McpAppConfig) => McpRequestHandler;
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { extractBearerToken } from "@remit/auth-service/verifier";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CONNECTED_APPS_PATH, CONSENT_PATH, DEVICE_PATH, LOGIN_PATH, MCP_PATH, mcpResource, } from "./auth/authorization-server.js";
|
|
4
|
+
import { MCP_SCOPES } from "./auth/scopes.js";
|
|
5
|
+
import { errorPage, pageError } from "./pages/render.js";
|
|
6
|
+
import { connectedAppsView, consentView, deviceView, loginView, } from "./pages/views.js";
|
|
7
|
+
import { createMcpHandler } from "./server.js";
|
|
8
|
+
const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
9
|
+
const RETURN_PATHS = new Set([
|
|
10
|
+
DEVICE_PATH,
|
|
11
|
+
CONNECTED_APPS_PATH,
|
|
12
|
+
]);
|
|
13
|
+
const connectedAppsSchema = z.object({
|
|
14
|
+
apps: z.array(z.object({
|
|
15
|
+
clientId: z.string(),
|
|
16
|
+
clientName: z.string(),
|
|
17
|
+
scopes: z.array(z.string()),
|
|
18
|
+
connectedAt: z.coerce.date(),
|
|
19
|
+
})),
|
|
20
|
+
});
|
|
21
|
+
const redirectSchema = z.object({ url: z.string() });
|
|
22
|
+
const failureSchema = z.object({
|
|
23
|
+
message: z.string().optional(),
|
|
24
|
+
error_description: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const failureOf = async (response) => {
|
|
27
|
+
const failure = failureSchema.parse(await response.json());
|
|
28
|
+
return (failure.error_description ??
|
|
29
|
+
failure.message ??
|
|
30
|
+
`The request failed with status ${response.status}.`);
|
|
31
|
+
};
|
|
32
|
+
const formString = (form, name) => {
|
|
33
|
+
const value = form.get(name);
|
|
34
|
+
return typeof value === "string" ? value : "";
|
|
35
|
+
};
|
|
36
|
+
const redirectWithCookies = (location, from) => {
|
|
37
|
+
const headers = new Headers({ location, "cache-control": "no-store" });
|
|
38
|
+
for (const cookie of from.headers.getSetCookie()) {
|
|
39
|
+
headers.append("set-cookie", cookie);
|
|
40
|
+
}
|
|
41
|
+
return new Response(null, { status: 302, headers });
|
|
42
|
+
};
|
|
43
|
+
export const createMcpApp = ({ origin, authRoutes, verifier, deps, }) => {
|
|
44
|
+
const resource = mcpResource(origin);
|
|
45
|
+
const resourceMetadataPath = `${PROTECTED_RESOURCE_METADATA_PATH}${MCP_PATH}`;
|
|
46
|
+
const challenge = (error) => new Response(JSON.stringify({ error: error ?? "unauthorized", resource }), {
|
|
47
|
+
status: 401,
|
|
48
|
+
headers: {
|
|
49
|
+
"content-type": "application/json",
|
|
50
|
+
"www-authenticate": [
|
|
51
|
+
"Bearer",
|
|
52
|
+
` resource_metadata="${origin}${resourceMetadataPath}",`,
|
|
53
|
+
` scope="${MCP_SCOPES.join(" ")}"`,
|
|
54
|
+
error ? `, error="${error}"` : "",
|
|
55
|
+
].join(""),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const continueFrom = async (response) => {
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return errorPage("Connecting failed", await failureOf(response), "Go back to your MCP client and connect remit again.", response.status);
|
|
61
|
+
}
|
|
62
|
+
const { url } = redirectSchema.parse(await response.json());
|
|
63
|
+
return redirectWithCookies(url, response);
|
|
64
|
+
};
|
|
65
|
+
const disabledPage = () => errorPage("Access refused", "This account is disabled.", "Ask the person who runs this remit server to restore your account.", 403);
|
|
66
|
+
const toLogin = (returnTo) => {
|
|
67
|
+
if (authRoutes.browser.kind === "host") {
|
|
68
|
+
return errorPage("Not signed in", "This server could not tell who you are.", "Sign in to remit, then open this page again.", 401);
|
|
69
|
+
}
|
|
70
|
+
return new Response(null, {
|
|
71
|
+
status: 302,
|
|
72
|
+
headers: {
|
|
73
|
+
location: `${LOGIN_PATH}?${new URLSearchParams({ return_to: returnTo })}`,
|
|
74
|
+
"cache-control": "no-store",
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const returnToOf = (raw) => {
|
|
79
|
+
if (!raw.startsWith("/") || raw.startsWith("//"))
|
|
80
|
+
return CONNECTED_APPS_PATH;
|
|
81
|
+
const url = new URL(raw, origin);
|
|
82
|
+
if (url.origin !== origin || !RETURN_PATHS.has(url.pathname)) {
|
|
83
|
+
return CONNECTED_APPS_PATH;
|
|
84
|
+
}
|
|
85
|
+
return `${url.pathname}${url.search}`;
|
|
86
|
+
};
|
|
87
|
+
const login = async (request, signIn) => {
|
|
88
|
+
if (request.method === "GET") {
|
|
89
|
+
const url = new URL(request.url);
|
|
90
|
+
if (url.search === "" || url.searchParams.has("return_to")) {
|
|
91
|
+
return loginView({
|
|
92
|
+
return_to: returnToOf(url.searchParams.get("return_to") ?? ""),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return loginView({ oauth_query: url.search.slice(1) });
|
|
96
|
+
}
|
|
97
|
+
const form = await request.formData();
|
|
98
|
+
const oauthQuery = formString(form, "oauth_query");
|
|
99
|
+
const returnTo = returnToOf(formString(form, "return_to"));
|
|
100
|
+
const hiddenFields = oauthQuery
|
|
101
|
+
? { oauth_query: oauthQuery }
|
|
102
|
+
: { return_to: returnTo };
|
|
103
|
+
const email = formString(form, "email");
|
|
104
|
+
const response = await signIn(request.headers, {
|
|
105
|
+
email,
|
|
106
|
+
password: formString(form, "password"),
|
|
107
|
+
oauth_query: oauthQuery,
|
|
108
|
+
});
|
|
109
|
+
if (response.status === 401) {
|
|
110
|
+
return loginView(hiddenFields, 401, email, pageError("Wrong email or password.", "Check both and try again. It is the password you sign in to remit with."));
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 429) {
|
|
113
|
+
return loginView(hiddenFields, 429, email, pageError("Too many sign-in attempts.", "Wait a minute, then try again."));
|
|
114
|
+
}
|
|
115
|
+
if (!oauthQuery && response.ok) {
|
|
116
|
+
await response.body?.cancel();
|
|
117
|
+
return redirectWithCookies(returnTo, response);
|
|
118
|
+
}
|
|
119
|
+
return continueFrom(response);
|
|
120
|
+
};
|
|
121
|
+
const consent = async (request) => {
|
|
122
|
+
if (request.method === "GET") {
|
|
123
|
+
const url = new URL(request.url);
|
|
124
|
+
const clientId = url.searchParams.get("client_id") ?? "";
|
|
125
|
+
const scopes = (url.searchParams.get("scope") ?? "")
|
|
126
|
+
.split(" ")
|
|
127
|
+
.filter((scope) => scope.length > 0);
|
|
128
|
+
if ((await authRoutes.sessionOf(request.headers)) === "disabled") {
|
|
129
|
+
return disabledPage();
|
|
130
|
+
}
|
|
131
|
+
const clientName = await authRoutes.clientName(request.headers, clientId);
|
|
132
|
+
if (!clientName) {
|
|
133
|
+
return errorPage("Connecting failed", "This sign-in request names an app remit does not know.", "Go back to your MCP client and connect remit again.", 400);
|
|
134
|
+
}
|
|
135
|
+
return consentView(url.search.slice(1), clientName, scopes);
|
|
136
|
+
}
|
|
137
|
+
const form = await request.formData();
|
|
138
|
+
return continueFrom(await authRoutes.consent(request.headers, {
|
|
139
|
+
accept: formString(form, "accept") === "true",
|
|
140
|
+
oauth_query: formString(form, "oauth_query"),
|
|
141
|
+
}));
|
|
142
|
+
};
|
|
143
|
+
const invalidCode = (userCode) => deviceView({
|
|
144
|
+
kind: "enter",
|
|
145
|
+
userCode,
|
|
146
|
+
error: pageError("That code is not valid or has expired.", "Run remit mcp login again and enter the new code."),
|
|
147
|
+
}, 400);
|
|
148
|
+
const device = async (request) => {
|
|
149
|
+
const url = new URL(request.url);
|
|
150
|
+
const session = await authRoutes.sessionOf(request.headers);
|
|
151
|
+
if (session === "disabled")
|
|
152
|
+
return disabledPage();
|
|
153
|
+
if (session === "absent") {
|
|
154
|
+
return toLogin(`${url.pathname}${url.search}`);
|
|
155
|
+
}
|
|
156
|
+
if (request.method === "GET") {
|
|
157
|
+
const userCode = (url.searchParams.get("user_code") ?? "").trim();
|
|
158
|
+
if (!userCode)
|
|
159
|
+
return deviceView({ kind: "enter", userCode: "" });
|
|
160
|
+
if (!(await authRoutes.claimDeviceCode(request.headers, userCode))) {
|
|
161
|
+
return invalidCode(userCode);
|
|
162
|
+
}
|
|
163
|
+
return deviceView({ kind: "confirm", userCode });
|
|
164
|
+
}
|
|
165
|
+
const form = await request.formData();
|
|
166
|
+
const userCode = formString(form, "user_code");
|
|
167
|
+
const approve = formString(form, "action") === "approve";
|
|
168
|
+
if (!(await authRoutes.claimDeviceCode(request.headers, userCode))) {
|
|
169
|
+
return invalidCode(userCode);
|
|
170
|
+
}
|
|
171
|
+
const response = await authRoutes.decideDeviceCode(request.headers, userCode, approve);
|
|
172
|
+
if (response.status === 401) {
|
|
173
|
+
return toLogin(`${DEVICE_PATH}?${new URLSearchParams({ user_code: userCode })}`);
|
|
174
|
+
}
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
return deviceView({
|
|
177
|
+
kind: "enter",
|
|
178
|
+
userCode,
|
|
179
|
+
error: pageError(await failureOf(response), "Run remit mcp login again and enter the new code."),
|
|
180
|
+
}, response.status);
|
|
181
|
+
}
|
|
182
|
+
await response.body?.cancel();
|
|
183
|
+
return deviceView({ kind: approve ? "approved" : "denied" });
|
|
184
|
+
};
|
|
185
|
+
const listConnectedApps = async (request) => {
|
|
186
|
+
const response = await authRoutes.connectedApps(request.headers);
|
|
187
|
+
if (response.status === 401) {
|
|
188
|
+
await response.body?.cancel();
|
|
189
|
+
return toLogin(CONNECTED_APPS_PATH);
|
|
190
|
+
}
|
|
191
|
+
if (response.status === 403) {
|
|
192
|
+
await response.body?.cancel();
|
|
193
|
+
return disabledPage();
|
|
194
|
+
}
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new Error(`Listing connected apps failed with ${response.status}: ${await response.text()}`);
|
|
197
|
+
}
|
|
198
|
+
return connectedAppsSchema.parse(await response.json()).apps;
|
|
199
|
+
};
|
|
200
|
+
const connectedApps = async (request) => {
|
|
201
|
+
if (request.method === "GET") {
|
|
202
|
+
const apps = await listConnectedApps(request);
|
|
203
|
+
if (apps instanceof Response)
|
|
204
|
+
return apps;
|
|
205
|
+
return connectedAppsView(apps);
|
|
206
|
+
}
|
|
207
|
+
const form = await request.formData();
|
|
208
|
+
const clientId = formString(form, "client_id");
|
|
209
|
+
const before = await listConnectedApps(request);
|
|
210
|
+
if (before instanceof Response)
|
|
211
|
+
return before;
|
|
212
|
+
const name = before.find((app) => app.clientId === clientId)?.clientName ?? clientId;
|
|
213
|
+
const response = await authRoutes.revokeClient(request.headers, clientId);
|
|
214
|
+
if (response.status === 401)
|
|
215
|
+
return toLogin(CONNECTED_APPS_PATH);
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
return connectedAppsView(before, response.status, undefined, pageError(`Disconnecting ${name} failed: ${await failureOf(response)}`, "Reload the page and try again."));
|
|
218
|
+
}
|
|
219
|
+
await response.body?.cancel();
|
|
220
|
+
const after = await listConnectedApps(request);
|
|
221
|
+
if (after instanceof Response)
|
|
222
|
+
return after;
|
|
223
|
+
return connectedAppsView(after, 200, name);
|
|
224
|
+
};
|
|
225
|
+
const serveMcp = async (request) => {
|
|
226
|
+
const bearer = extractBearerToken(request.headers.get("authorization") ?? undefined);
|
|
227
|
+
if (!bearer)
|
|
228
|
+
return challenge();
|
|
229
|
+
const verification = await verifier.verify(bearer);
|
|
230
|
+
if (!verification.ok)
|
|
231
|
+
return challenge("invalid_token");
|
|
232
|
+
return createMcpHandler(verification.principal, deps)(request);
|
|
233
|
+
};
|
|
234
|
+
return async (request) => {
|
|
235
|
+
const { pathname } = new URL(request.url);
|
|
236
|
+
if (pathname === "/.well-known/oauth-authorization-server") {
|
|
237
|
+
return authRoutes.serverMetadata(request);
|
|
238
|
+
}
|
|
239
|
+
if (pathname === resourceMetadataPath ||
|
|
240
|
+
pathname === PROTECTED_RESOURCE_METADATA_PATH) {
|
|
241
|
+
return Response.json({
|
|
242
|
+
resource,
|
|
243
|
+
authorization_servers: [origin],
|
|
244
|
+
scopes_supported: [...MCP_SCOPES],
|
|
245
|
+
bearer_methods_supported: ["header"],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (pathname === MCP_PATH)
|
|
249
|
+
return serveMcp(request);
|
|
250
|
+
if (pathname === LOGIN_PATH && authRoutes.browser.kind === "signIn") {
|
|
251
|
+
return login(request, authRoutes.browser.signIn);
|
|
252
|
+
}
|
|
253
|
+
if (pathname === CONSENT_PATH)
|
|
254
|
+
return consent(request);
|
|
255
|
+
if (pathname === DEVICE_PATH)
|
|
256
|
+
return device(request);
|
|
257
|
+
if (pathname === CONNECTED_APPS_PATH)
|
|
258
|
+
return connectedApps(request);
|
|
259
|
+
return authRoutes.handle(request);
|
|
260
|
+
};
|
|
261
|
+
};
|