@fragno-dev/mcp-fragment 0.0.0-stage → 0.0.3
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.md +16 -0
- package/README.md +312 -2
- package/bin/run.js +7 -0
- package/dist/browser/client/react.d.ts +253 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +194 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +256 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +129 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +256 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +131 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +283 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +159 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +256 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +127 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-BCzNDSlW.d.ts +711 -0
- package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
- package/dist/browser/index.d.ts +813 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/src-B6dHZXqB.js +2303 -0
- package/dist/browser/src-B6dHZXqB.js.map +1 -0
- package/dist/cli/cli.d.ts +6 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +227 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/definition.js +330 -0
- package/dist/cli/definition.js.map +1 -0
- package/dist/cli/diagnostics.js +93 -0
- package/dist/cli/diagnostics.js.map +1 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mcp-api.js +87 -0
- package/dist/cli/mcp-api.js.map +1 -0
- package/dist/cli/mcp-types.js +100 -0
- package/dist/cli/mcp-types.js.map +1 -0
- package/dist/cli/oauth-provider.js +113 -0
- package/dist/cli/oauth-provider.js.map +1 -0
- package/dist/cli/routes.js +743 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/schema.js +13 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/cli/services.js +330 -0
- package/dist/cli/services.js.map +1 -0
- package/dist/node/definition.d.ts +260 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +330 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/diagnostics.js +93 -0
- package/dist/node/diagnostics.js.map +1 -0
- package/dist/node/index.d.ts +694 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +26 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/mcp-api.d.ts +2 -0
- package/dist/node/mcp-api.js +87 -0
- package/dist/node/mcp-api.js.map +1 -0
- package/dist/node/mcp-types.d.ts +146 -0
- package/dist/node/mcp-types.d.ts.map +1 -0
- package/dist/node/mcp-types.js +100 -0
- package/dist/node/mcp-types.js.map +1 -0
- package/dist/node/oauth-provider.d.ts +24 -0
- package/dist/node/oauth-provider.d.ts.map +1 -0
- package/dist/node/oauth-provider.js +113 -0
- package/dist/node/oauth-provider.js.map +1 -0
- package/dist/node/routes.d.ts +466 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +743 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +9 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +13 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/services.d.ts +40 -0
- package/dist/node/services.d.ts.map +1 -0
- package/dist/node/services.js +330 -0
- package/dist/node/services.js.map +1 -0
- package/dist/node/testing/mcp-test-server.d.ts +42 -0
- package/dist/node/testing/mcp-test-server.d.ts.map +1 -0
- package/dist/node/testing/mcp-test-server.js +218 -0
- package/dist/node/testing/mcp-test-server.js.map +1 -0
- package/package.json +130 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { parseSecretPayload } from "./mcp-api.js";
|
|
2
|
+
import { mcpToolSchema } from "./mcp-types.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/diagnostics.ts
|
|
5
|
+
const serverConnectionCacheOutputSchema = z.object({
|
|
6
|
+
protocolVersion: z.string().nullable().optional(),
|
|
7
|
+
serverInfo: z.unknown().nullable().optional(),
|
|
8
|
+
capabilities: z.unknown().nullable().optional(),
|
|
9
|
+
tools: z.array(mcpToolSchema).nullable().optional(),
|
|
10
|
+
updatedAt: z.union([z.string(), z.date()]).optional()
|
|
11
|
+
});
|
|
12
|
+
const serverOutputSchema = z.object({
|
|
13
|
+
slug: z.string(),
|
|
14
|
+
name: z.string().nullable().optional(),
|
|
15
|
+
endpointUrl: z.string(),
|
|
16
|
+
authMode: z.string(),
|
|
17
|
+
cache: serverConnectionCacheOutputSchema.nullable().optional()
|
|
18
|
+
});
|
|
19
|
+
const refreshOutputSchema = z.object({
|
|
20
|
+
ok: z.boolean(),
|
|
21
|
+
tools: z.array(mcpToolSchema),
|
|
22
|
+
stage: z.enum(["auth", "list_tools"]).nullable(),
|
|
23
|
+
checkedAt: z.string(),
|
|
24
|
+
server: serverOutputSchema.omit({ cache: true }),
|
|
25
|
+
auth: z.object({
|
|
26
|
+
authenticated: z.boolean(),
|
|
27
|
+
mode: z.string(),
|
|
28
|
+
tokenPresent: z.boolean(),
|
|
29
|
+
expiresAt: z.union([z.string(), z.date()]).nullable(),
|
|
30
|
+
expired: z.boolean().nullable(),
|
|
31
|
+
scopes: z.object({
|
|
32
|
+
requested: z.array(z.string()).nullable(),
|
|
33
|
+
granted: z.array(z.string()).nullable(),
|
|
34
|
+
missing: z.array(z.string()).nullable(),
|
|
35
|
+
raw: z.string().nullable()
|
|
36
|
+
})
|
|
37
|
+
}),
|
|
38
|
+
live: z.object({
|
|
39
|
+
reachable: z.boolean(),
|
|
40
|
+
listToolsOk: z.boolean(),
|
|
41
|
+
toolCount: z.number().nullable(),
|
|
42
|
+
protocolVersion: z.string().nullable(),
|
|
43
|
+
serverInfo: z.unknown().nullable(),
|
|
44
|
+
capabilities: z.unknown().nullable()
|
|
45
|
+
}),
|
|
46
|
+
cache: z.object({
|
|
47
|
+
presentBeforeCheck: z.boolean(),
|
|
48
|
+
previousToolCount: z.number().nullable(),
|
|
49
|
+
updatedToolCount: z.number().nullable()
|
|
50
|
+
}),
|
|
51
|
+
error: z.object({
|
|
52
|
+
code: z.string(),
|
|
53
|
+
message: z.string()
|
|
54
|
+
}).nullable()
|
|
55
|
+
});
|
|
56
|
+
const splitScope = (scope) => {
|
|
57
|
+
const scopes = scope?.split(/\s+/).filter(Boolean) ?? [];
|
|
58
|
+
return scopes.length > 0 ? scopes : null;
|
|
59
|
+
};
|
|
60
|
+
const newestOAuthScope = (states) => {
|
|
61
|
+
const [state] = states.toSorted((left, right) => new Date(right.expiresAt).getTime() - new Date(left.expiresAt).getTime());
|
|
62
|
+
return splitScope(state?.scope);
|
|
63
|
+
};
|
|
64
|
+
const buildAuthDiagnostics = ({ mode, secrets, oauthStates, authChanges }) => {
|
|
65
|
+
const authSecret = secrets.find((secret) => secret.kind === "auth");
|
|
66
|
+
const authPayload = authChanges?.authPayload ?? authSecret?.payload;
|
|
67
|
+
const authExpiresAt = authChanges?.authExpiresAt ?? authSecret?.expiresAt ?? null;
|
|
68
|
+
const parsedAuth = authPayload ? parseSecretPayload(authPayload) : void 0;
|
|
69
|
+
const granted = splitScope(parsedAuth?.type === "oauth" || parsedAuth?.type === "client_credentials" ? parsedAuth.tokens?.scope : void 0);
|
|
70
|
+
const requested = parsedAuth?.type === "client_credentials" ? parsedAuth.scopes ?? null : mode === "oauth" ? newestOAuthScope(oauthStates) : null;
|
|
71
|
+
const grantedScopes = granted ? new Set(granted) : null;
|
|
72
|
+
const missing = requested && grantedScopes ? requested.filter((scope) => !grantedScopes.has(scope)) : null;
|
|
73
|
+
const tokenPresent = parsedAuth?.type === "bearer" ? Boolean(parsedAuth.token) : Boolean(parsedAuth?.tokens?.access_token);
|
|
74
|
+
const expired = authExpiresAt ? new Date(authExpiresAt).getTime() <= Date.now() : null;
|
|
75
|
+
return {
|
|
76
|
+
authenticated: mode === "none" || tokenPresent,
|
|
77
|
+
mode,
|
|
78
|
+
tokenPresent,
|
|
79
|
+
expiresAt: authExpiresAt,
|
|
80
|
+
expired,
|
|
81
|
+
scopes: {
|
|
82
|
+
requested,
|
|
83
|
+
granted,
|
|
84
|
+
missing,
|
|
85
|
+
raw: parsedAuth?.type === "oauth" || parsedAuth?.type === "client_credentials" ? parsedAuth.tokens?.scope ?? null : null
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
90
|
+
//#endregion
|
|
91
|
+
export { buildAuthDiagnostics, errorMessage, refreshOutputSchema };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","names":[],"sources":["../../src/diagnostics.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport { parseSecretPayload } from \"./mcp-api\";\nimport { mcpToolSchema } from \"./mcp-types\";\nimport type { AuthPersistenceChanges } from \"./services\";\n\nconst serverConnectionCacheOutputSchema = z.object({\n protocolVersion: z.string().nullable().optional(),\n serverInfo: z.unknown().nullable().optional(),\n capabilities: z.unknown().nullable().optional(),\n tools: z.array(mcpToolSchema).nullable().optional(),\n updatedAt: z.union([z.string(), z.date()]).optional(),\n});\n\nconst serverOutputSchema = z.object({\n slug: z.string(),\n name: z.string().nullable().optional(),\n endpointUrl: z.string(),\n authMode: z.string(),\n cache: serverConnectionCacheOutputSchema.nullable().optional(),\n});\n\nexport const refreshOutputSchema = z.object({\n ok: z.boolean(),\n tools: z.array(mcpToolSchema),\n stage: z.enum([\"auth\", \"list_tools\"]).nullable(),\n checkedAt: z.string(),\n server: serverOutputSchema.omit({ cache: true }),\n auth: z.object({\n authenticated: z.boolean(),\n mode: z.string(),\n tokenPresent: z.boolean(),\n expiresAt: z.union([z.string(), z.date()]).nullable(),\n expired: z.boolean().nullable(),\n scopes: z.object({\n requested: z.array(z.string()).nullable(),\n granted: z.array(z.string()).nullable(),\n missing: z.array(z.string()).nullable(),\n raw: z.string().nullable(),\n }),\n }),\n live: z.object({\n reachable: z.boolean(),\n listToolsOk: z.boolean(),\n toolCount: z.number().nullable(),\n protocolVersion: z.string().nullable(),\n serverInfo: z.unknown().nullable(),\n capabilities: z.unknown().nullable(),\n }),\n cache: z.object({\n presentBeforeCheck: z.boolean(),\n previousToolCount: z.number().nullable(),\n updatedToolCount: z.number().nullable(),\n }),\n error: z.object({ code: z.string(), message: z.string() }).nullable(),\n});\n\nexport type SecretForDiagnostics = {\n id: { toString(): string } | string;\n kind: string;\n payload: string;\n expiresAt?: Date | string | null;\n};\n\nexport type OAuthStateForDiagnostics = {\n scope?: string | null;\n expiresAt: Date | string;\n};\n\ntype AuthPayloadForDiagnostics =\n | { type: \"bearer\"; token?: string }\n | { type: \"oauth\"; tokens?: { access_token?: string; scope?: string } }\n | {\n type: \"client_credentials\";\n scopes?: string[];\n tokens?: { access_token?: string; scope?: string };\n };\n\nconst splitScope = (scope: string | null | undefined) => {\n const scopes = scope?.split(/\\s+/).filter(Boolean) ?? [];\n return scopes.length > 0 ? scopes : null;\n};\n\nconst newestOAuthScope = (states: OAuthStateForDiagnostics[]) => {\n const [state] = states.toSorted(\n (left, right) => new Date(right.expiresAt).getTime() - new Date(left.expiresAt).getTime(),\n );\n return splitScope(state?.scope);\n};\n\nexport const buildAuthDiagnostics = ({\n mode,\n secrets,\n oauthStates,\n authChanges,\n}: {\n mode: string;\n secrets: SecretForDiagnostics[];\n oauthStates: OAuthStateForDiagnostics[];\n authChanges?: AuthPersistenceChanges;\n}) => {\n const authSecret = secrets.find((secret) => secret.kind === \"auth\");\n const authPayload = authChanges?.authPayload ?? authSecret?.payload;\n const authExpiresAt = authChanges?.authExpiresAt ?? authSecret?.expiresAt ?? null;\n const parsedAuth = authPayload\n ? parseSecretPayload<AuthPayloadForDiagnostics>(authPayload)\n : undefined;\n const granted = splitScope(\n parsedAuth?.type === \"oauth\" || parsedAuth?.type === \"client_credentials\"\n ? parsedAuth.tokens?.scope\n : undefined,\n );\n const requested =\n parsedAuth?.type === \"client_credentials\"\n ? (parsedAuth.scopes ?? null)\n : mode === \"oauth\"\n ? newestOAuthScope(oauthStates)\n : null;\n const grantedScopes = granted ? new Set(granted) : null;\n const missing =\n requested && grantedScopes ? requested.filter((scope) => !grantedScopes.has(scope)) : null;\n const tokenPresent =\n parsedAuth?.type === \"bearer\"\n ? Boolean(parsedAuth.token)\n : Boolean(parsedAuth?.tokens?.access_token);\n const expired = authExpiresAt ? new Date(authExpiresAt).getTime() <= Date.now() : null;\n\n return {\n authenticated: mode === \"none\" || tokenPresent,\n mode,\n tokenPresent,\n expiresAt: authExpiresAt,\n expired,\n scopes: {\n requested,\n granted,\n missing,\n raw:\n parsedAuth?.type === \"oauth\" || parsedAuth?.type === \"client_credentials\"\n ? (parsedAuth.tokens?.scope ?? null)\n : null,\n },\n };\n};\n\nexport const errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mcpFragmentDefinition } from "./definition.js";
|
|
2
|
+
import { mcpRoutesFactory } from "./routes.js";
|
|
3
|
+
import "@fragno-dev/core/client";
|
|
4
|
+
import { instantiate } from "@fragno-dev/core";
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
const routes = [mcpRoutesFactory];
|
|
7
|
+
function createMcpFragment(config, fragnoConfig) {
|
|
8
|
+
return instantiate(mcpFragmentDefinition).withConfig(config).withRoutes(routes).withOptions(fragnoConfig).build();
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { createMcpFragment };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { createClientBuilder, type FragnoPublicClientConfig } from \"@fragno-dev/core/client\";\n\nimport { instantiate } from \"@fragno-dev/core\";\nimport type { FragnoPublicConfigWithDatabase } from \"@fragno-dev/db\";\n\nimport { mcpFragmentDefinition, type McpFragmentConfig } from \"./definition\";\nimport { mcpRoutesFactory } from \"./routes\";\n\nconst routes = [mcpRoutesFactory] as const;\n\nexport function createMcpFragment(\n config: McpFragmentConfig,\n fragnoConfig: FragnoPublicConfigWithDatabase,\n) {\n return instantiate(mcpFragmentDefinition)\n .withConfig(config)\n .withRoutes(routes)\n .withOptions(fragnoConfig)\n .build();\n}\n\nexport function createMcpFragmentClients(fragnoConfig: FragnoPublicClientConfig = {}) {\n const builder = createClientBuilder(mcpFragmentDefinition, fragnoConfig, routes);\n\n return {\n useServers: builder.createHook(\"/servers\"),\n useServer: builder.createHook(\"/servers/:slug\"),\n createServer: builder.createMutator(\"POST\", \"/servers\"),\n refreshServer: builder.createMutator(\"POST\", \"/servers/:slug/refresh\"),\n callTool: builder.createMutator(\"POST\", \"/servers/:slug/tools/execute\"),\n setToken: builder.createMutator(\"POST\", \"/servers/:slug/auth/token\"),\n startOAuth: builder.createMutator(\"POST\", \"/servers/:slug/auth/start\"),\n deleteAuth: builder.createMutator(\"DELETE\", \"/servers/:slug/auth\"),\n };\n}\n"],"mappings":";;;;;AAQA,MAAM,SAAS,CAAC,gBAAgB;AAEhC,SAAgB,kBACd,QACA,cACA;CACA,OAAO,YAAY,qBAAqB,CAAC,CACtC,WAAW,MAAM,CAAC,CAClB,WAAW,MAAM,CAAC,CAClB,YAAY,YAAY,CAAC,CACzB,MAAM;AACX"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
2
|
+
//#region src/mcp-api.ts
|
|
3
|
+
function assertAllowedEndpoint(endpointUrl, config) {
|
|
4
|
+
const url = new URL(endpointUrl);
|
|
5
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("MCP endpoint must be an HTTP(S) URL");
|
|
6
|
+
if (config.allowedEndpointUrls && !config.allowedEndpointUrls(url)) throw new Error("MCP endpoint is not allowed");
|
|
7
|
+
return url;
|
|
8
|
+
}
|
|
9
|
+
function stringifySecretPayload(value) {
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
12
|
+
function parseSecretPayload(value) {
|
|
13
|
+
return JSON.parse(value);
|
|
14
|
+
}
|
|
15
|
+
function bearerRequestInit(token) {
|
|
16
|
+
if (!token) return;
|
|
17
|
+
return { headers: { Authorization: `Bearer ${token}` } };
|
|
18
|
+
}
|
|
19
|
+
const authProvider = (auth) => auth?.type === "provider" ? auth.provider : void 0;
|
|
20
|
+
const authRequestInit = (auth) => auth?.type === "bearer" ? bearerRequestInit(auth.token) : void 0;
|
|
21
|
+
async function readAuthChanges(auth) {
|
|
22
|
+
return auth?.type === "provider" ? await auth.readAuthChanges() : void 0;
|
|
23
|
+
}
|
|
24
|
+
async function withMcpClient(args) {
|
|
25
|
+
const client = new Client({
|
|
26
|
+
name: "fragno-mcp-fragment",
|
|
27
|
+
version: "0.1.0"
|
|
28
|
+
}, {
|
|
29
|
+
capabilities: {},
|
|
30
|
+
versionNegotiation: { mode: "auto" }
|
|
31
|
+
});
|
|
32
|
+
const transport = new StreamableHTTPClientTransport(new URL(args.endpointUrl), {
|
|
33
|
+
fetch: args.fetchImplementation,
|
|
34
|
+
requestInit: authRequestInit(args.auth),
|
|
35
|
+
authProvider: authProvider(args.auth)
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await client.connect(transport, { timeout: args.timeoutMs });
|
|
39
|
+
const capabilities = client.getServerCapabilities();
|
|
40
|
+
const metadata = {
|
|
41
|
+
protocolVersion: client.getNegotiatedProtocolVersion(),
|
|
42
|
+
serverInfo: client.getServerVersion(),
|
|
43
|
+
capabilities
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
result: await args.operation(client),
|
|
48
|
+
metadata,
|
|
49
|
+
authChanges: await readAuthChanges(args.auth)
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error,
|
|
55
|
+
authChanges: await readAuthChanges(args.auth)
|
|
56
|
+
};
|
|
57
|
+
} finally {
|
|
58
|
+
await transport.close().catch(() => void 0);
|
|
59
|
+
await client.close().catch(() => void 0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function listMcpTools(args) {
|
|
63
|
+
return await withMcpClient({
|
|
64
|
+
...args,
|
|
65
|
+
operation: (client) => client.listTools(void 0, { timeout: args.timeoutMs })
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function callMcpTool(args) {
|
|
69
|
+
return await withMcpClient({
|
|
70
|
+
...args,
|
|
71
|
+
operation: async (client) => {
|
|
72
|
+
const result = await client.callTool({
|
|
73
|
+
name: args.name,
|
|
74
|
+
arguments: args.toolArguments ?? {}
|
|
75
|
+
}, { timeout: args.timeoutMs });
|
|
76
|
+
return {
|
|
77
|
+
isError: result.isError === true,
|
|
78
|
+
content: Array.isArray(result.content) ? result.content : [],
|
|
79
|
+
structuredContent: result.structuredContent ?? null
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { assertAllowedEndpoint, callMcpTool, listMcpTools, parseSecretPayload, stringifySecretPayload };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=mcp-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-api.js","names":[],"sources":["../../src/mcp-api.ts"],"sourcesContent":["import {\n Client,\n StreamableHTTPClientTransport,\n type OAuthClientProvider,\n} from \"@modelcontextprotocol/client\";\n\nimport type { McpFragmentConfig, McpToolCallResult } from \"./mcp-types\";\n\nexport interface StoredServerAuth {\n mode: string;\n payload?: unknown;\n}\n\nexport interface McpConnectionMetadata {\n protocolVersion?: string;\n serverInfo?: unknown;\n capabilities?: unknown;\n}\n\nexport type McpClientAuth<TAuthChanges = unknown> =\n | { type: \"bearer\"; token?: string }\n | {\n type: \"provider\";\n provider: OAuthClientProvider;\n readAuthChanges: () => Promise<TAuthChanges | undefined>;\n };\n\nexport type McpOperationResult<T, TAuthChanges = unknown> =\n | {\n ok: true;\n result: T;\n metadata: McpConnectionMetadata;\n authChanges?: TAuthChanges;\n }\n | { ok: false; error: unknown; authChanges?: TAuthChanges };\n\nexport function assertAllowedEndpoint(endpointUrl: string, config: McpFragmentConfig) {\n const url = new URL(endpointUrl);\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"MCP endpoint must be an HTTP(S) URL\");\n }\n if (config.allowedEndpointUrls && !config.allowedEndpointUrls(url)) {\n throw new Error(\"MCP endpoint is not allowed\");\n }\n return url;\n}\n\nexport function stringifySecretPayload(value: unknown) {\n return JSON.stringify(value);\n}\n\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Secret consumers select the stored payload shape at this trusted serialization boundary.\nexport function parseSecretPayload<T>(value: string) {\n return JSON.parse(value) as T;\n}\n\nfunction bearerRequestInit(token: string | undefined): RequestInit | undefined {\n if (!token) {\n return undefined;\n }\n return { headers: { Authorization: `Bearer ${token}` } };\n}\n\nconst authProvider = (auth: McpClientAuth | undefined) =>\n auth?.type === \"provider\" ? auth.provider : undefined;\n\nconst authRequestInit = (auth: McpClientAuth | undefined) =>\n auth?.type === \"bearer\" ? bearerRequestInit(auth.token) : undefined;\n\nasync function readAuthChanges<TAuthChanges>(auth: McpClientAuth<TAuthChanges> | undefined) {\n return auth?.type === \"provider\" ? await auth.readAuthChanges() : undefined;\n}\n\nasync function withMcpClient<T, TAuthChanges = unknown>(args: {\n endpointUrl: string;\n auth?: McpClientAuth<TAuthChanges>;\n timeoutMs?: number;\n fetchImplementation?: typeof fetch;\n operation: (client: Client) => Promise<T>;\n}): Promise<McpOperationResult<T, TAuthChanges>> {\n const client = new Client(\n { name: \"fragno-mcp-fragment\", version: \"0.1.0\" },\n { capabilities: {}, versionNegotiation: { mode: \"auto\" } },\n );\n const transport = new StreamableHTTPClientTransport(new URL(args.endpointUrl), {\n fetch: args.fetchImplementation,\n requestInit: authRequestInit(args.auth),\n authProvider: authProvider(args.auth),\n });\n\n try {\n await client.connect(transport, { timeout: args.timeoutMs });\n const capabilities = client.getServerCapabilities();\n\n const metadata: McpConnectionMetadata = {\n protocolVersion: client.getNegotiatedProtocolVersion(),\n serverInfo: client.getServerVersion(),\n capabilities,\n };\n const result = await args.operation(client);\n return { ok: true, result, metadata, authChanges: await readAuthChanges(args.auth) };\n } catch (error) {\n return { ok: false, error, authChanges: await readAuthChanges(args.auth) };\n } finally {\n await transport.close().catch(() => undefined);\n await client.close().catch(() => undefined);\n }\n}\n\nexport async function listMcpTools<TAuthChanges = unknown>(args: {\n endpointUrl: string;\n auth?: McpClientAuth<TAuthChanges>;\n timeoutMs?: number;\n fetchImplementation?: typeof fetch;\n}) {\n return await withMcpClient({\n ...args,\n operation: (client) => client.listTools(undefined, { timeout: args.timeoutMs }),\n });\n}\n\nexport async function callMcpTool<TAuthChanges = unknown>(args: {\n endpointUrl: string;\n auth?: McpClientAuth<TAuthChanges>;\n name: string;\n toolArguments?: Record<string, unknown>;\n timeoutMs?: number;\n fetchImplementation?: typeof fetch;\n}) {\n return await withMcpClient({\n ...args,\n operation: async (client) => {\n const result = await client.callTool(\n { name: args.name, arguments: args.toolArguments ?? {} },\n { timeout: args.timeoutMs },\n );\n // Tool errors are results for the caller, not protocol failures (MCP spec, tools/call).\n return {\n isError: result.isError === true,\n content: Array.isArray(result.content) ? result.content : [],\n structuredContent: result.structuredContent ?? null,\n } satisfies McpToolCallResult;\n },\n });\n}\n"],"mappings":";;AAoCA,SAAgB,sBAAsB,aAAqB,QAA2B;CACpF,MAAM,MAAM,IAAI,IAAI,WAAW;CAC/B,IAAI,IAAI,aAAa,YAAY,IAAI,aAAa,SAChD,MAAM,IAAI,MAAM,qCAAqC;CAEvD,IAAI,OAAO,uBAAuB,CAAC,OAAO,oBAAoB,GAAG,GAC/D,MAAM,IAAI,MAAM,6BAA6B;CAE/C,OAAO;AACT;AAEA,SAAgB,uBAAuB,OAAgB;CACrD,OAAO,KAAK,UAAU,KAAK;AAC7B;AAGA,SAAgB,mBAAsB,OAAe;CACnD,OAAO,KAAK,MAAM,KAAK;AACzB;AAEA,SAAS,kBAAkB,OAAoD;CAC7E,IAAI,CAAC,OACH;CAEF,OAAO,EAAE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAAE;AACzD;AAEA,MAAM,gBAAgB,SACpB,MAAM,SAAS,aAAa,KAAK,WAAW,KAAA;AAE9C,MAAM,mBAAmB,SACvB,MAAM,SAAS,WAAW,kBAAkB,KAAK,KAAK,IAAI,KAAA;AAE5D,eAAe,gBAA8B,MAA+C;CAC1F,OAAO,MAAM,SAAS,aAAa,MAAM,KAAK,gBAAgB,IAAI,KAAA;AACpE;AAEA,eAAe,cAAyC,MAMP;CAC/C,MAAM,SAAS,IAAI,OACjB;EAAE,MAAM;EAAuB,SAAS;CAAQ,GAChD;EAAE,cAAc,CAAC;EAAG,oBAAoB,EAAE,MAAM,OAAO;CAAE,CAC3D;CACA,MAAM,YAAY,IAAI,8BAA8B,IAAI,IAAI,KAAK,WAAW,GAAG;EAC7E,OAAO,KAAK;EACZ,aAAa,gBAAgB,KAAK,IAAI;EACtC,cAAc,aAAa,KAAK,IAAI;CACtC,CAAC;CAED,IAAI;EACF,MAAM,OAAO,QAAQ,WAAW,EAAE,SAAS,KAAK,UAAU,CAAC;EAC3D,MAAM,eAAe,OAAO,sBAAsB;EAElD,MAAM,WAAkC;GACtC,iBAAiB,OAAO,6BAA6B;GACrD,YAAY,OAAO,iBAAiB;GACpC;EACF;EAEA,OAAO;GAAE,IAAI;GAAM,QAAA,MADE,KAAK,UAAU,MAAM;GACf;GAAU,aAAa,MAAM,gBAAgB,KAAK,IAAI;EAAE;CACrF,SAAS,OAAO;EACd,OAAO;GAAE,IAAI;GAAO;GAAO,aAAa,MAAM,gBAAgB,KAAK,IAAI;EAAE;CAC3E,UAAU;EACR,MAAM,UAAU,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;EAC7C,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,KAAA,CAAS;CAC5C;AACF;AAEA,eAAsB,aAAqC,MAKxD;CACD,OAAO,MAAM,cAAc;EACzB,GAAG;EACH,YAAY,WAAW,OAAO,UAAU,KAAA,GAAW,EAAE,SAAS,KAAK,UAAU,CAAC;CAChF,CAAC;AACH;AAEA,eAAsB,YAAoC,MAOvD;CACD,OAAO,MAAM,cAAc;EACzB,GAAG;EACH,WAAW,OAAO,WAAW;GAC3B,MAAM,SAAS,MAAM,OAAO,SAC1B;IAAE,MAAM,KAAK;IAAM,WAAW,KAAK,iBAAiB,CAAC;GAAE,GACvD,EAAE,SAAS,KAAK,UAAU,CAC5B;GAEA,OAAO;IACL,SAAS,OAAO,YAAY;IAC5B,SAAS,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,UAAU,CAAC;IAC3D,mBAAmB,OAAO,qBAAqB;GACjD;EACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/mcp-types.ts
|
|
3
|
+
const authConfigSchema = z.discriminatedUnion("type", [
|
|
4
|
+
z.object({ type: z.literal("none") }),
|
|
5
|
+
z.object({
|
|
6
|
+
type: z.literal("bearer"),
|
|
7
|
+
token: z.string().min(1)
|
|
8
|
+
}),
|
|
9
|
+
z.object({
|
|
10
|
+
type: z.literal("client_credentials"),
|
|
11
|
+
clientId: z.string().min(1),
|
|
12
|
+
clientSecret: z.string().min(1),
|
|
13
|
+
scopes: z.array(z.string()).optional()
|
|
14
|
+
}),
|
|
15
|
+
z.object({
|
|
16
|
+
type: z.literal("oauth"),
|
|
17
|
+
clientId: z.string().optional(),
|
|
18
|
+
clientSecret: z.string().optional(),
|
|
19
|
+
scopes: z.array(z.string()).optional()
|
|
20
|
+
})
|
|
21
|
+
]);
|
|
22
|
+
/** Slugs are URL path segments and OAuth state prefixes, so they exclude ':' and dot segments. */
|
|
23
|
+
const mcpServerSlugSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/);
|
|
24
|
+
const createServerInputSchema = z.object({
|
|
25
|
+
slug: mcpServerSlugSchema,
|
|
26
|
+
name: z.string().optional(),
|
|
27
|
+
endpointUrl: z.url(),
|
|
28
|
+
auth: authConfigSchema.default({ type: "none" })
|
|
29
|
+
});
|
|
30
|
+
/** Replacement keeps the slug and creation time; everything else is supplied again. */
|
|
31
|
+
const replaceServerConfigurationInputSchema = createServerInputSchema.omit({ slug: true });
|
|
32
|
+
const toolCallInputSchema = z.object({
|
|
33
|
+
name: z.string().min(1),
|
|
34
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
35
|
+
timeoutMs: z.number().int().positive().max(12e4).optional()
|
|
36
|
+
});
|
|
37
|
+
const tokenAuthInputSchema = z.object({ token: z.string().min(1) });
|
|
38
|
+
const mcpCredentialsPresenceSchema = z.enum(["present", "missing"]).describe("Whether credentials are stored, not whether the server accepts them.");
|
|
39
|
+
/** Sanitized auth state derived from stored configuration; it never proves live server access. */
|
|
40
|
+
const mcpAuthStatusSchema = z.discriminatedUnion("mode", [
|
|
41
|
+
z.object({ mode: z.literal("none") }),
|
|
42
|
+
z.object({
|
|
43
|
+
mode: z.literal("bearer"),
|
|
44
|
+
credentials: mcpCredentialsPresenceSchema
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
mode: z.literal("client_credentials"),
|
|
48
|
+
credentials: mcpCredentialsPresenceSchema
|
|
49
|
+
}),
|
|
50
|
+
z.object({
|
|
51
|
+
mode: z.literal("oauth"),
|
|
52
|
+
state: z.enum([
|
|
53
|
+
"consent-required",
|
|
54
|
+
"consent-pending",
|
|
55
|
+
"authorized",
|
|
56
|
+
"expired"
|
|
57
|
+
]).describe("Expired means the access token expired and no refresh token is stored; pending means an unexpired authorization link exists.")
|
|
58
|
+
})
|
|
59
|
+
]);
|
|
60
|
+
/** The pending link embeds the callback state, so reading it needs server-create authority. */
|
|
61
|
+
const mcpOAuthPendingSchema = z.object({ pending: z.object({
|
|
62
|
+
authorizationUrl: z.url(),
|
|
63
|
+
expiresAt: z.union([z.string(), z.date()])
|
|
64
|
+
}).nullable().describe("The newest unexpired, unconsumed authorization link; any pending link completes it.") });
|
|
65
|
+
/**
|
|
66
|
+
* Mirrors the MCP protocol's tool definition, whose optional members are defined by the spec.
|
|
67
|
+
* Loose so newer spec members, such as icons, survive the cache and the API.
|
|
68
|
+
*/
|
|
69
|
+
const mcpToolSchema = z.looseObject({
|
|
70
|
+
name: z.string(),
|
|
71
|
+
title: z.string().optional(),
|
|
72
|
+
description: z.string().optional(),
|
|
73
|
+
inputSchema: z.record(z.string(), z.unknown()),
|
|
74
|
+
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
75
|
+
annotations: z.record(z.string(), z.unknown()).optional(),
|
|
76
|
+
_meta: z.record(z.string(), z.unknown()).optional()
|
|
77
|
+
});
|
|
78
|
+
/** Tool errors are results the model should see; transport and auth failures are route errors. */
|
|
79
|
+
const mcpToolCallResultSchema = z.object({
|
|
80
|
+
isError: z.boolean(),
|
|
81
|
+
content: z.array(z.record(z.string(), z.unknown())),
|
|
82
|
+
/** Since MCP 2026-07-28 any JSON value; null when the tool returned none. */
|
|
83
|
+
structuredContent: z.unknown()
|
|
84
|
+
});
|
|
85
|
+
/** Query parameter carrying the OAuth callback URI for an MCP OAuth start request. */
|
|
86
|
+
const MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER = "redirectUri";
|
|
87
|
+
const mcpOAuthRedirectUriSchema = z.url().refine((value) => {
|
|
88
|
+
const protocol = new URL(value).protocol;
|
|
89
|
+
return protocol === "https:" || protocol === "http:";
|
|
90
|
+
}, "MCP OAuth redirect URI must use HTTP or HTTPS");
|
|
91
|
+
const oauthStartInputSchema = z.object({
|
|
92
|
+
scope: z.string().optional(),
|
|
93
|
+
clientId: z.string().min(1).optional(),
|
|
94
|
+
clientSecret: z.string().min(1).optional(),
|
|
95
|
+
discardTokens: z.boolean().default(false).describe("Drop stored tokens so status reports pending consent until this new consent completes.")
|
|
96
|
+
});
|
|
97
|
+
//#endregion
|
|
98
|
+
export { MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER, authConfigSchema, createServerInputSchema, mcpAuthStatusSchema, mcpOAuthPendingSchema, mcpOAuthRedirectUriSchema, mcpServerSlugSchema, mcpToolCallResultSchema, mcpToolSchema, oauthStartInputSchema, replaceServerConfigurationInputSchema, tokenAuthInputSchema, toolCallInputSchema };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=mcp-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-types.js","names":[],"sources":["../../src/mcp-types.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport interface McpFragmentConfig {\n /** Optional URL allow-list. If omitted, only http(s) URL syntax is checked. */\n allowedEndpointUrls?: (url: URL) => boolean;\n /** Allows OAuth callback URLs. OAuth start is rejected when this policy is omitted. */\n allowedOAuthRedirectUris?: (url: URL) => boolean;\n /** Optional fetch implementation for tests/custom runtimes. */\n fetch?: typeof fetch;\n}\n\nexport const authConfigSchema = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"none\") }),\n z.object({ type: z.literal(\"bearer\"), token: z.string().min(1) }),\n z.object({\n type: z.literal(\"client_credentials\"),\n clientId: z.string().min(1),\n clientSecret: z.string().min(1),\n scopes: z.array(z.string()).optional(),\n }),\n z.object({\n type: z.literal(\"oauth\"),\n clientId: z.string().optional(),\n clientSecret: z.string().optional(),\n scopes: z.array(z.string()).optional(),\n }),\n]);\n\n/** Slugs are URL path segments and OAuth state prefixes, so they exclude ':' and dot segments. */\nexport const mcpServerSlugSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/);\n\nexport const createServerInputSchema = z.object({\n slug: mcpServerSlugSchema,\n name: z.string().optional(),\n endpointUrl: z.url(),\n auth: authConfigSchema.default({ type: \"none\" }),\n});\n\n/** Replacement keeps the slug and creation time; everything else is supplied again. */\nexport const replaceServerConfigurationInputSchema = createServerInputSchema.omit({ slug: true });\n\nexport const toolCallInputSchema = z.object({\n name: z.string().min(1),\n arguments: z.record(z.string(), z.unknown()).optional(),\n timeoutMs: z.number().int().positive().max(120_000).optional(),\n});\n\nexport const tokenAuthInputSchema = z.object({\n token: z.string().min(1),\n});\n\nconst mcpCredentialsPresenceSchema = z\n .enum([\"present\", \"missing\"])\n .describe(\"Whether credentials are stored, not whether the server accepts them.\");\n\n/** Sanitized auth state derived from stored configuration; it never proves live server access. */\nexport const mcpAuthStatusSchema = z.discriminatedUnion(\"mode\", [\n z.object({ mode: z.literal(\"none\") }),\n z.object({ mode: z.literal(\"bearer\"), credentials: mcpCredentialsPresenceSchema }),\n z.object({ mode: z.literal(\"client_credentials\"), credentials: mcpCredentialsPresenceSchema }),\n z.object({\n mode: z.literal(\"oauth\"),\n state: z\n .enum([\"consent-required\", \"consent-pending\", \"authorized\", \"expired\"])\n .describe(\n \"Expired means the access token expired and no refresh token is stored; pending means an unexpired authorization link exists.\",\n ),\n }),\n]);\n\n/** The pending link embeds the callback state, so reading it needs server-create authority. */\nexport const mcpOAuthPendingSchema = z.object({\n pending: z\n .object({ authorizationUrl: z.url(), expiresAt: z.union([z.string(), z.date()]) })\n .nullable()\n .describe(\n \"The newest unexpired, unconsumed authorization link; any pending link completes it.\",\n ),\n});\n\n/**\n * Mirrors the MCP protocol's tool definition, whose optional members are defined by the spec.\n * Loose so newer spec members, such as icons, survive the cache and the API.\n */\nexport const mcpToolSchema = z.looseObject({\n name: z.string(),\n title: z.string().optional(),\n description: z.string().optional(),\n inputSchema: z.record(z.string(), z.unknown()),\n outputSchema: z.record(z.string(), z.unknown()).optional(),\n annotations: z.record(z.string(), z.unknown()).optional(),\n _meta: z.record(z.string(), z.unknown()).optional(),\n});\n\n/** Tool errors are results the model should see; transport and auth failures are route errors. */\nexport const mcpToolCallResultSchema = z.object({\n isError: z.boolean(),\n content: z.array(z.record(z.string(), z.unknown())),\n /** Since MCP 2026-07-28 any JSON value; null when the tool returned none. */\n structuredContent: z.unknown(),\n});\n\n/** Query parameter carrying the OAuth callback URI for an MCP OAuth start request. */\nexport const MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER = \"redirectUri\";\n\nexport const mcpOAuthRedirectUriSchema = z.url().refine((value) => {\n const protocol = new URL(value).protocol;\n return protocol === \"https:\" || protocol === \"http:\";\n}, \"MCP OAuth redirect URI must use HTTP or HTTPS\");\n\nexport const oauthStartInputSchema = z.object({\n scope: z.string().optional(),\n clientId: z.string().min(1).optional(),\n clientSecret: z.string().min(1).optional(),\n discardTokens: z\n .boolean()\n .default(false)\n .describe(\n \"Drop stored tokens so status reports pending consent until this new consent completes.\",\n ),\n});\n\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type CreateServerInput = z.infer<typeof createServerInputSchema>;\nexport type ToolCallInput = z.infer<typeof toolCallInputSchema>;\nexport type McpAuthStatus = z.infer<typeof mcpAuthStatusSchema>;\nexport type McpOAuthPending = z.infer<typeof mcpOAuthPendingSchema>;\nexport type McpTool = z.infer<typeof mcpToolSchema>;\nexport type McpToolCallResult = z.infer<typeof mcpToolCallResultSchema>;\n"],"mappings":";;AAWA,MAAa,mBAAmB,EAAE,mBAAmB,QAAQ;CAC3D,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,EAAE,CAAC;CACpC,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,QAAQ;EAAG,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAAE,CAAC;CAChE,EAAE,OAAO;EACP,MAAM,EAAE,QAAQ,oBAAoB;EACpC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EAC1B,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;EAC9B,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;CACvC,CAAC;CACD,EAAE,OAAO;EACP,MAAM,EAAE,QAAQ,OAAO;EACvB,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;EAC9B,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;EAClC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;CACvC,CAAC;AACH,CAAC;;AAGD,MAAa,sBAAsB,EAAE,OAAO,CAAC,CAAC,MAAM,sBAAsB;AAE1E,MAAa,0BAA0B,EAAE,OAAO;CAC9C,MAAM;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,aAAa,EAAE,IAAI;CACnB,MAAM,iBAAiB,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjD,CAAC;;AAGD,MAAa,wCAAwC,wBAAwB,KAAK,EAAE,MAAM,KAAK,CAAC;AAEhG,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CACtD,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,IAAO,CAAC,CAAC,SAAS;AAC/D,CAAC;AAED,MAAa,uBAAuB,EAAE,OAAO,EAC3C,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACzB,CAAC;AAED,MAAM,+BAA+B,EAClC,KAAK,CAAC,WAAW,SAAS,CAAC,CAAC,CAC5B,SAAS,sEAAsE;;AAGlF,MAAa,sBAAsB,EAAE,mBAAmB,QAAQ;CAC9D,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,EAAE,CAAC;CACpC,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,QAAQ;EAAG,aAAa;CAA6B,CAAC;CACjF,EAAE,OAAO;EAAE,MAAM,EAAE,QAAQ,oBAAoB;EAAG,aAAa;CAA6B,CAAC;CAC7F,EAAE,OAAO;EACP,MAAM,EAAE,QAAQ,OAAO;EACvB,OAAO,EACJ,KAAK;GAAC;GAAoB;GAAmB;GAAc;EAAS,CAAC,CAAC,CACtE,SACC,8HACF;CACJ,CAAC;AACH,CAAC;;AAGD,MAAa,wBAAwB,EAAE,OAAO,EAC5C,SAAS,EACN,OAAO;CAAE,kBAAkB,EAAE,IAAI;CAAG,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC;AAAE,CAAC,CAAC,CACjF,SAAS,CAAC,CACV,SACC,qFACF,EACJ,CAAC;;;;;AAMD,MAAa,gBAAgB,EAAE,YAAY;CACzC,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,aAAa,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CAC7C,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CACzD,aAAa,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;CACxD,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS;AACpD,CAAC;;AAGD,MAAa,0BAA0B,EAAE,OAAO;CAC9C,SAAS,EAAE,QAAQ;CACnB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC;;CAElD,mBAAmB,EAAE,QAAQ;AAC/B,CAAC;;AAGD,MAAa,yCAAyC;AAEtD,MAAa,4BAA4B,EAAE,IAAI,CAAC,CAAC,QAAQ,UAAU;CACjE,MAAM,WAAW,IAAI,IAAI,KAAK,CAAC,CAAC;CAChC,OAAO,aAAa,YAAY,aAAa;AAC/C,GAAG,+CAA+C;AAElD,MAAa,wBAAwB,EAAE,OAAO;CAC5C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACrC,cAAc,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACzC,eAAe,EACZ,QAAQ,CAAC,CACT,QAAQ,KAAK,CAAC,CACd,SACC,wFACF;AACJ,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region src/oauth-provider.ts
|
|
2
|
+
var BufferedOAuthClientProvider = class {
|
|
3
|
+
snapshot;
|
|
4
|
+
changes = {};
|
|
5
|
+
constructor(snapshot) {
|
|
6
|
+
this.snapshot = snapshot;
|
|
7
|
+
}
|
|
8
|
+
get redirectUrl() {
|
|
9
|
+
return this.snapshot.redirectUrl;
|
|
10
|
+
}
|
|
11
|
+
get clientMetadata() {
|
|
12
|
+
if (this.snapshot.flow === "client_credentials") return {
|
|
13
|
+
redirect_uris: [],
|
|
14
|
+
token_endpoint_auth_method: "client_secret_basic",
|
|
15
|
+
grant_types: ["client_credentials"],
|
|
16
|
+
client_name: this.snapshot.clientName ?? "Fragno MCP Fragment",
|
|
17
|
+
scope: this.snapshot.scope
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
redirect_uris: this.snapshot.redirectUrl ? [this.snapshot.redirectUrl] : [],
|
|
21
|
+
token_endpoint_auth_method: this.snapshot.clientSecret ? "client_secret_basic" : "none",
|
|
22
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
23
|
+
response_types: ["code"],
|
|
24
|
+
client_name: this.snapshot.clientName ?? "Fragno MCP Fragment",
|
|
25
|
+
scope: this.snapshot.scope
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
state() {
|
|
29
|
+
if (!this.snapshot.stateId) throw new Error("OAuth state is required to start authorization");
|
|
30
|
+
return this.snapshot.stateId;
|
|
31
|
+
}
|
|
32
|
+
clientInformation() {
|
|
33
|
+
if (this.changes.clientInformation) return this.changes.clientInformation;
|
|
34
|
+
if (this.snapshot.clientId) {
|
|
35
|
+
const clientInformation = {
|
|
36
|
+
client_id: this.snapshot.clientId,
|
|
37
|
+
client_secret: this.snapshot.clientSecret
|
|
38
|
+
};
|
|
39
|
+
this.changes.clientInformation = clientInformation;
|
|
40
|
+
return clientInformation;
|
|
41
|
+
}
|
|
42
|
+
return this.changes.clientInvalidated ? void 0 : this.snapshot.clientInformation;
|
|
43
|
+
}
|
|
44
|
+
saveClientInformation(clientInformation) {
|
|
45
|
+
this.changes.clientInformation = clientInformation;
|
|
46
|
+
}
|
|
47
|
+
tokens() {
|
|
48
|
+
return this.changes.tokens ?? (this.changes.tokensInvalidated ? void 0 : this.snapshot.tokens);
|
|
49
|
+
}
|
|
50
|
+
saveTokens(tokens) {
|
|
51
|
+
this.changes.tokens = tokens;
|
|
52
|
+
}
|
|
53
|
+
redirectToAuthorization(authorizationUrl) {
|
|
54
|
+
this.changes.authorizationUrl = authorizationUrl;
|
|
55
|
+
}
|
|
56
|
+
saveCodeVerifier(codeVerifier) {
|
|
57
|
+
if (this.snapshot.flow === "client_credentials") return;
|
|
58
|
+
if (!this.snapshot.stateId || !this.snapshot.redirectUrl) throw new Error("OAuth state and redirect URL are required");
|
|
59
|
+
this.changes.oauthState = {
|
|
60
|
+
id: this.snapshot.stateId,
|
|
61
|
+
codeVerifier,
|
|
62
|
+
redirectUri: this.snapshot.redirectUrl,
|
|
63
|
+
scope: this.snapshot.scope,
|
|
64
|
+
expiresAt: new Date(Date.now() + 6e5)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
codeVerifier() {
|
|
68
|
+
if (this.snapshot.flow === "client_credentials") throw new Error("codeVerifier is not used for client_credentials flow");
|
|
69
|
+
const state = this.snapshot.oauthState;
|
|
70
|
+
if (!state || state.consumedAt) throw new Error("Invalid OAuth state");
|
|
71
|
+
if (state.expiresAt.getTime() < Date.now()) throw new Error("OAuth state expired");
|
|
72
|
+
return state.codeVerifier;
|
|
73
|
+
}
|
|
74
|
+
saveDiscoveryState(state) {
|
|
75
|
+
this.changes.discoveryState = state;
|
|
76
|
+
}
|
|
77
|
+
discoveryState() {
|
|
78
|
+
return this.changes.discoveryState ?? (this.changes.discoveryInvalidated ? void 0 : this.snapshot.discoveryState);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The SDK retries authorization right after invalidating, so later reads skip what it dropped:
|
|
82
|
+
* retrying a rejected client or token fails the same way. A configured client is configuration,
|
|
83
|
+
* not a registration, so it is kept. The code verifier lives in the pending OAuth state, which
|
|
84
|
+
* the callback consumes either way.
|
|
85
|
+
*/
|
|
86
|
+
invalidateCredentials(scope) {
|
|
87
|
+
if (scope === "all" || scope === "tokens") {
|
|
88
|
+
this.changes.tokens = void 0;
|
|
89
|
+
this.changes.tokensInvalidated = true;
|
|
90
|
+
}
|
|
91
|
+
if (scope === "all" || scope === "client") {
|
|
92
|
+
this.changes.clientInformation = void 0;
|
|
93
|
+
this.changes.clientInvalidated = true;
|
|
94
|
+
}
|
|
95
|
+
if (scope === "all" || scope === "discovery") {
|
|
96
|
+
this.changes.discoveryState = void 0;
|
|
97
|
+
this.changes.discoveryInvalidated = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
prepareTokenRequest(scope) {
|
|
101
|
+
if (this.snapshot.flow !== "client_credentials") return;
|
|
102
|
+
const params = new URLSearchParams({ grant_type: "client_credentials" });
|
|
103
|
+
if (scope) params.set("scope", scope);
|
|
104
|
+
return params;
|
|
105
|
+
}
|
|
106
|
+
consumeState() {
|
|
107
|
+
if (this.snapshot.stateId) this.changes.consumeStateId = this.snapshot.stateId;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { BufferedOAuthClientProvider };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=oauth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-provider.js","names":[],"sources":["../../src/oauth-provider.ts"],"sourcesContent":["import type {\n OAuthClientProvider,\n OAuthDiscoveryState,\n OAuthClientInformationMixed,\n OAuthClientMetadata,\n OAuthTokens,\n} from \"@modelcontextprotocol/client\";\n\nexport interface OAuthProviderSnapshot {\n serverId: string;\n endpointUrl: string;\n redirectUrl?: string;\n scope?: string;\n stateId?: string;\n clientId?: string;\n clientSecret?: string;\n clientName?: string;\n clientInformation?: OAuthClientInformationMixed;\n tokens?: OAuthTokens;\n discoveryState?: OAuthDiscoveryState;\n flow?: \"authorization_code\" | \"client_credentials\";\n oauthState?: {\n id: string;\n codeVerifier: string;\n redirectUri: string;\n scope?: string;\n expiresAt: Date;\n consumedAt?: Date | null;\n };\n}\n\nexport interface OAuthProviderChanges {\n authorizationUrl?: URL;\n clientInformation?: OAuthClientInformationMixed;\n tokens?: OAuthTokens;\n discoveryState?: OAuthDiscoveryState;\n oauthState?: {\n id: string;\n codeVerifier: string;\n redirectUri: string;\n scope?: string;\n expiresAt: Date;\n };\n consumeStateId?: string;\n /** The server rejected the stored tokens, so they must be removed unless new ones were saved. */\n tokensInvalidated?: boolean;\n /** The server rejected the registered client, so it must be removed unless a new one was saved. */\n clientInvalidated?: boolean;\n /** The discovered metadata failed, so it must be removed unless it was discovered again. */\n discoveryInvalidated?: boolean;\n}\n\nexport class BufferedOAuthClientProvider implements OAuthClientProvider {\n readonly changes: OAuthProviderChanges = {};\n\n constructor(private readonly snapshot: OAuthProviderSnapshot) {}\n\n get redirectUrl() {\n return this.snapshot.redirectUrl;\n }\n\n get clientMetadata(): OAuthClientMetadata {\n if (this.snapshot.flow === \"client_credentials\") {\n return {\n redirect_uris: [],\n token_endpoint_auth_method: \"client_secret_basic\",\n grant_types: [\"client_credentials\"],\n client_name: this.snapshot.clientName ?? \"Fragno MCP Fragment\",\n scope: this.snapshot.scope,\n };\n }\n return {\n redirect_uris: this.snapshot.redirectUrl ? [this.snapshot.redirectUrl] : [],\n token_endpoint_auth_method: this.snapshot.clientSecret ? \"client_secret_basic\" : \"none\",\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n client_name: this.snapshot.clientName ?? \"Fragno MCP Fragment\",\n scope: this.snapshot.scope,\n };\n }\n\n state() {\n if (!this.snapshot.stateId) {\n throw new Error(\"OAuth state is required to start authorization\");\n }\n return this.snapshot.stateId;\n }\n\n clientInformation() {\n // Preserve the SDK's issuer stamp instead of rebuilding static credentials after discovery.\n if (this.changes.clientInformation) {\n return this.changes.clientInformation;\n }\n if (this.snapshot.clientId) {\n const clientInformation = {\n client_id: this.snapshot.clientId,\n client_secret: this.snapshot.clientSecret,\n };\n this.changes.clientInformation = clientInformation;\n return clientInformation;\n }\n return this.changes.clientInvalidated ? undefined : this.snapshot.clientInformation;\n }\n\n saveClientInformation(clientInformation: OAuthClientInformationMixed) {\n this.changes.clientInformation = clientInformation;\n }\n\n tokens() {\n return (\n this.changes.tokens ?? (this.changes.tokensInvalidated ? undefined : this.snapshot.tokens)\n );\n }\n\n saveTokens(tokens: OAuthTokens) {\n this.changes.tokens = tokens;\n }\n\n redirectToAuthorization(authorizationUrl: URL) {\n this.changes.authorizationUrl = authorizationUrl;\n }\n\n saveCodeVerifier(codeVerifier: string) {\n if (this.snapshot.flow === \"client_credentials\") {\n return;\n }\n if (!this.snapshot.stateId || !this.snapshot.redirectUrl) {\n throw new Error(\"OAuth state and redirect URL are required\");\n }\n this.changes.oauthState = {\n id: this.snapshot.stateId,\n codeVerifier,\n redirectUri: this.snapshot.redirectUrl,\n scope: this.snapshot.scope,\n expiresAt: new Date(Date.now() + 10 * 60 * 1000),\n };\n }\n\n codeVerifier() {\n if (this.snapshot.flow === \"client_credentials\") {\n throw new Error(\"codeVerifier is not used for client_credentials flow\");\n }\n const state = this.snapshot.oauthState;\n if (!state || state.consumedAt) {\n throw new Error(\"Invalid OAuth state\");\n }\n if (state.expiresAt.getTime() < Date.now()) {\n throw new Error(\"OAuth state expired\");\n }\n return state.codeVerifier;\n }\n\n saveDiscoveryState(state: OAuthDiscoveryState) {\n this.changes.discoveryState = state;\n }\n\n discoveryState() {\n return (\n this.changes.discoveryState ??\n (this.changes.discoveryInvalidated ? undefined : this.snapshot.discoveryState)\n );\n }\n\n /**\n * The SDK retries authorization right after invalidating, so later reads skip what it dropped:\n * retrying a rejected client or token fails the same way. A configured client is configuration,\n * not a registration, so it is kept. The code verifier lives in the pending OAuth state, which\n * the callback consumes either way.\n */\n invalidateCredentials(scope: \"all\" | \"client\" | \"tokens\" | \"verifier\" | \"discovery\") {\n if (scope === \"all\" || scope === \"tokens\") {\n this.changes.tokens = undefined;\n this.changes.tokensInvalidated = true;\n }\n if (scope === \"all\" || scope === \"client\") {\n this.changes.clientInformation = undefined;\n this.changes.clientInvalidated = true;\n }\n if (scope === \"all\" || scope === \"discovery\") {\n this.changes.discoveryState = undefined;\n this.changes.discoveryInvalidated = true;\n }\n }\n\n prepareTokenRequest(scope?: string) {\n if (this.snapshot.flow !== \"client_credentials\") {\n return undefined;\n }\n const params = new URLSearchParams({ grant_type: \"client_credentials\" });\n if (scope) {\n params.set(\"scope\", scope);\n }\n return params;\n }\n\n consumeState() {\n if (this.snapshot.stateId) {\n this.changes.consumeStateId = this.snapshot.stateId;\n }\n }\n}\n"],"mappings":"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"}
|