@forwardreach/saas-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/management.d.ts +80 -0
- package/dist/client/management.js +78 -0
- package/dist/core/http.d.ts +27 -0
- package/dist/core/http.js +29 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/redaction.d.ts +11 -0
- package/dist/core/redaction.js +43 -0
- package/dist/core/scopes.d.ts +13 -0
- package/dist/core/scopes.js +35 -0
- package/dist/core/types.d.ts +256 -0
- package/dist/core/types.js +1 -0
- package/dist/core/urls.d.ts +69 -0
- package/dist/core/urls.js +85 -0
- package/dist/hono/index.d.ts +1 -0
- package/dist/hono/index.js +1 -0
- package/dist/hono/routes.d.ts +212 -0
- package/dist/hono/routes.js +535 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/better-auth.d.ts +84 -0
- package/dist/node/better-auth.js +77 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +2 -0
- package/dist/node/oauth.d.ts +18 -0
- package/dist/node/oauth.js +39 -0
- package/dist/react/McpActivity.d.ts +32 -0
- package/dist/react/McpActivity.js +182 -0
- package/dist/react/McpConnectedClients.d.ts +27 -0
- package/dist/react/McpConnectedClients.js +34 -0
- package/dist/react/McpConnectorOverview.d.ts +21 -0
- package/dist/react/McpConnectorOverview.js +12 -0
- package/dist/react/McpConsentPanel.d.ts +47 -0
- package/dist/react/McpConsentPanel.js +18 -0
- package/dist/react/McpDeveloperTokens.d.ts +27 -0
- package/dist/react/McpDeveloperTokens.js +46 -0
- package/dist/react/McpToolReference.d.ts +17 -0
- package/dist/react/McpToolReference.js +19 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/mcp-settings-shared.d.ts +29 -0
- package/dist/react/mcp-settings-shared.js +37 -0
- package/package.json +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @forwardreach/saas-mcp
|
|
2
|
+
|
|
3
|
+
This package is published for use by ReachMe.chat, PeopleThread, and approved ReachMe/PeopleThread partners.
|
|
4
|
+
|
|
5
|
+
It is not intended as a general-purpose public package. Full integration and development documentation is maintained in the private `forwardreach/saas-shared` repository for users with access:
|
|
6
|
+
|
|
7
|
+
https://github.com/forwardreach/saas-shared/blob/main/docs/packages/saas-mcp.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./management.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./management.js";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { McpAuditEventFilters, McpAuditEventListResponse, McpConnectionDetailResponse, McpConnectionListResponse, McpConnectionRevokeResponse, McpCreateDeveloperTokenInput, McpDeveloperTokenCreateResponse, McpDeveloperTokenListResponse, McpDeveloperTokenRevokeResponse, McpScopeValue } from "../core/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Product-specific management route overrides used by the shared settings UI.
|
|
4
|
+
*
|
|
5
|
+
* Defaults are `/workspaces/{workspaceId}/mcp/...`; products mounted under a
|
|
6
|
+
* prefix such as `/api` should override these paths or provide an `apiBaseUrl`.
|
|
7
|
+
*/
|
|
8
|
+
export interface McpManagementClientRoutes {
|
|
9
|
+
/** GET: list connected OAuth grants. */
|
|
10
|
+
connections?: string;
|
|
11
|
+
/** GET: connection detail for one OAuth grant. */
|
|
12
|
+
connectionDetail?: (grantId: string) => string;
|
|
13
|
+
/** POST: revoke one OAuth grant and its active tokens. */
|
|
14
|
+
revokeConnection?: (grantId: string) => string;
|
|
15
|
+
/** GET/POST: list or create developer tokens. */
|
|
16
|
+
developerTokens?: string;
|
|
17
|
+
/** POST: revoke one developer token. */
|
|
18
|
+
revokeDeveloperToken?: (tokenId: string) => string;
|
|
19
|
+
/** GET: list audit events with query-string filters. */
|
|
20
|
+
auditEvents?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Configuration for creating a browser-safe MCP management client. */
|
|
23
|
+
export interface CreateMcpManagementClientOptions {
|
|
24
|
+
/** Optional absolute API origin/base path; omitted paths resolve against the current origin. */
|
|
25
|
+
apiBaseUrl?: string;
|
|
26
|
+
/** Workspace whose MCP settings are being managed. */
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
/** Fetch implementation, useful for tests or non-browser runtimes. */
|
|
29
|
+
fetch?: typeof globalThis.fetch;
|
|
30
|
+
/** Static or lazy request headers, for example CSRF or product auth headers. */
|
|
31
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
32
|
+
/** Optional product route overrides. */
|
|
33
|
+
routes?: McpManagementClientRoutes;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Browser-side contract consumed by the MCP settings components (`McpConnectedClients`, `McpDeveloperTokens`, `McpActivity`).
|
|
37
|
+
*
|
|
38
|
+
* Products may use `createMcpManagementClient` or provide their own object with
|
|
39
|
+
* the same shape when they need custom transport, auth, caching, or error
|
|
40
|
+
* handling.
|
|
41
|
+
*/
|
|
42
|
+
export interface McpManagementClient<S extends McpScopeValue = McpScopeValue> {
|
|
43
|
+
/** List OAuth connections for the workspace. */
|
|
44
|
+
listConnections(input?: {
|
|
45
|
+
userId?: string;
|
|
46
|
+
includeRevoked?: boolean;
|
|
47
|
+
}): Promise<McpConnectionListResponse<S>>;
|
|
48
|
+
/** Load one OAuth connection and its recent activity. */
|
|
49
|
+
getConnection(grantId: string, input?: {
|
|
50
|
+
userId?: string;
|
|
51
|
+
includeRevoked?: boolean;
|
|
52
|
+
}): Promise<McpConnectionDetailResponse<S>>;
|
|
53
|
+
/** Revoke an OAuth grant for the workspace. */
|
|
54
|
+
revokeConnection(grantId: string, input?: {
|
|
55
|
+
userId?: string;
|
|
56
|
+
reason?: string | null;
|
|
57
|
+
}): Promise<McpConnectionRevokeResponse<S>>;
|
|
58
|
+
/** List manually created developer tokens for the workspace. */
|
|
59
|
+
listDeveloperTokens(input?: {
|
|
60
|
+
userId?: string;
|
|
61
|
+
includeRevoked?: boolean;
|
|
62
|
+
}): Promise<McpDeveloperTokenListResponse<S>>;
|
|
63
|
+
/** Create a developer token and return its one-time raw secret. */
|
|
64
|
+
createDeveloperToken(input: McpCreateDeveloperTokenInput<S>): Promise<McpDeveloperTokenCreateResponse<S>>;
|
|
65
|
+
/** Revoke a developer token without affecting OAuth grants. */
|
|
66
|
+
revokeDeveloperToken(tokenId: string, input?: {
|
|
67
|
+
userId?: string;
|
|
68
|
+
reason?: string | null;
|
|
69
|
+
}): Promise<McpDeveloperTokenRevokeResponse<S>>;
|
|
70
|
+
/** List audit events using server-side filters where supported by the product. */
|
|
71
|
+
listAuditEvents(filters?: McpAuditEventFilters): Promise<McpAuditEventListResponse>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a fetch-based client for the shared MCP management API shape.
|
|
75
|
+
*
|
|
76
|
+
* The client sends JSON bodies for mutations, serializes filters as query
|
|
77
|
+
* parameters for list endpoints, and throws an Error when the server responds
|
|
78
|
+
* with a non-2xx status.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createMcpManagementClient<S extends McpScopeValue = McpScopeValue>(options: CreateMcpManagementClientOptions): McpManagementClient<S>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
function defaultRoutes(workspaceId) {
|
|
2
|
+
const workspaceBase = `/workspaces/${encodeURIComponent(workspaceId)}/mcp`;
|
|
3
|
+
return {
|
|
4
|
+
connections: `${workspaceBase}/connections`,
|
|
5
|
+
connectionDetail: (grantId) => `${workspaceBase}/connections/${encodeURIComponent(grantId)}`,
|
|
6
|
+
revokeConnection: (grantId) => `${workspaceBase}/connections/${encodeURIComponent(grantId)}/revoke`,
|
|
7
|
+
developerTokens: `${workspaceBase}/developer-tokens`,
|
|
8
|
+
revokeDeveloperToken: (tokenId) => `${workspaceBase}/developer-tokens/${encodeURIComponent(tokenId)}/revoke`,
|
|
9
|
+
auditEvents: `${workspaceBase}/audit-events`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function urlWithQuery(apiBaseUrl, path, query) {
|
|
13
|
+
const url = new URL(path, apiBaseUrl ? `${apiBaseUrl.replace(/\/+$/, "")}/` : globalThis.location?.origin ?? "http://localhost");
|
|
14
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
15
|
+
if (value !== undefined && value !== null && value !== "")
|
|
16
|
+
url.searchParams.set(key, String(value));
|
|
17
|
+
}
|
|
18
|
+
if (!apiBaseUrl && path.startsWith("/"))
|
|
19
|
+
return `${url.pathname}${url.search}`;
|
|
20
|
+
return url.toString();
|
|
21
|
+
}
|
|
22
|
+
async function resolvedHeaders(headers) {
|
|
23
|
+
return typeof headers === "function" ? await headers() : headers ?? {};
|
|
24
|
+
}
|
|
25
|
+
async function parseJsonResponse(response) {
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const body = await response.json().catch(() => ({}));
|
|
28
|
+
const message = typeof body.error === "string" ? body.error : `MCP management request failed with ${response.status}`;
|
|
29
|
+
throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
return (await response.json());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a fetch-based client for the shared MCP management API shape.
|
|
35
|
+
*
|
|
36
|
+
* The client sends JSON bodies for mutations, serializes filters as query
|
|
37
|
+
* parameters for list endpoints, and throws an Error when the server responds
|
|
38
|
+
* with a non-2xx status.
|
|
39
|
+
*/
|
|
40
|
+
export function createMcpManagementClient(options) {
|
|
41
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
42
|
+
const routes = { ...defaultRoutes(options.workspaceId), ...(options.routes ?? {}) };
|
|
43
|
+
async function request(path, init = {}, query) {
|
|
44
|
+
const headers = await resolvedHeaders(options.headers);
|
|
45
|
+
const response = await fetchImpl(urlWithQuery(options.apiBaseUrl, path, query), {
|
|
46
|
+
...init,
|
|
47
|
+
headers: {
|
|
48
|
+
...headers,
|
|
49
|
+
...(init.body ? { "content-type": "application/json" } : {}),
|
|
50
|
+
...(init.headers ?? {})
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return parseJsonResponse(response);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
listConnections(input = {}) {
|
|
57
|
+
return request(routes.connections, {}, input);
|
|
58
|
+
},
|
|
59
|
+
getConnection(grantId, input = {}) {
|
|
60
|
+
return request(routes.connectionDetail(grantId), {}, input);
|
|
61
|
+
},
|
|
62
|
+
revokeConnection(grantId, input = {}) {
|
|
63
|
+
return request(routes.revokeConnection(grantId), { method: "POST", body: JSON.stringify(input) });
|
|
64
|
+
},
|
|
65
|
+
listDeveloperTokens(input = {}) {
|
|
66
|
+
return request(routes.developerTokens, {}, input);
|
|
67
|
+
},
|
|
68
|
+
createDeveloperToken(input) {
|
|
69
|
+
return request(routes.developerTokens, { method: "POST", body: JSON.stringify(input) });
|
|
70
|
+
},
|
|
71
|
+
revokeDeveloperToken(tokenId, input = {}) {
|
|
72
|
+
return request(routes.revokeDeveloperToken(tokenId), { method: "POST", body: JSON.stringify(input) });
|
|
73
|
+
},
|
|
74
|
+
listAuditEvents(filters = {}) {
|
|
75
|
+
return request(routes.auditEvents, {}, filters);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Inputs checked before accepting a remote HTTP MCP request. */
|
|
2
|
+
export interface McpHttpEnvelopeInput {
|
|
3
|
+
url: URL;
|
|
4
|
+
method: string;
|
|
5
|
+
headers: Headers;
|
|
6
|
+
requireHttps: boolean;
|
|
7
|
+
allowedOrigins: readonly string[];
|
|
8
|
+
allowedHosts: readonly string[];
|
|
9
|
+
supportedProtocolVersions: readonly string[];
|
|
10
|
+
forwardedProto?: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** Result of transport-level MCP HTTP validation. */
|
|
13
|
+
export type McpHttpEnvelopeValidation = {
|
|
14
|
+
ok: true;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
status: number;
|
|
18
|
+
message: string;
|
|
19
|
+
reason: "query_token" | "https_required" | "invalid_host" | "invalid_origin" | "unsupported_protocol";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Validate request-level MCP transport constraints before parsing JSON-RPC.
|
|
23
|
+
*
|
|
24
|
+
* This guards against unsupported protocol versions, insecure production
|
|
25
|
+
* traffic, untrusted hosts/origins, and access tokens sent in query strings.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateMcpHttpEnvelope(input: McpHttpEnvelopeInput): McpHttpEnvelopeValidation;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isLocalOrigin } from "./urls.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validate request-level MCP transport constraints before parsing JSON-RPC.
|
|
4
|
+
*
|
|
5
|
+
* This guards against unsupported protocol versions, insecure production
|
|
6
|
+
* traffic, untrusted hosts/origins, and access tokens sent in query strings.
|
|
7
|
+
*/
|
|
8
|
+
export function validateMcpHttpEnvelope(input) {
|
|
9
|
+
if (input.url.searchParams.has("access_token")) {
|
|
10
|
+
return { ok: false, status: 401, message: "Access tokens in the query string are not accepted", reason: "query_token" };
|
|
11
|
+
}
|
|
12
|
+
const isSecure = input.forwardedProto ? input.forwardedProto === "https" : input.url.protocol === "https:";
|
|
13
|
+
if (input.requireHttps && !isSecure && !isLocalOrigin(input.url.origin)) {
|
|
14
|
+
return { ok: false, status: 400, message: "HTTPS is required for remote MCP requests", reason: "https_required" };
|
|
15
|
+
}
|
|
16
|
+
const host = input.headers.get("host");
|
|
17
|
+
if (input.allowedHosts.length > 0 && (!host || !input.allowedHosts.includes(host))) {
|
|
18
|
+
return { ok: false, status: 403, message: "Invalid Host header", reason: "invalid_host" };
|
|
19
|
+
}
|
|
20
|
+
const origin = input.headers.get("origin");
|
|
21
|
+
if (origin && input.allowedOrigins.length > 0 && !input.allowedOrigins.includes("*") && !input.allowedOrigins.includes(origin)) {
|
|
22
|
+
return { ok: false, status: 403, message: "Invalid Origin header", reason: "invalid_origin" };
|
|
23
|
+
}
|
|
24
|
+
const protocolVersion = input.headers.get("mcp-protocol-version");
|
|
25
|
+
if (protocolVersion && !input.supportedProtocolVersions.includes(protocolVersion)) {
|
|
26
|
+
return { ok: false, status: 400, message: "Unsupported MCP protocol version", reason: "unsupported_protocol" };
|
|
27
|
+
}
|
|
28
|
+
return { ok: true };
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpAuditEvent, McpConnectionSummary } from "./types.js";
|
|
2
|
+
/** Marker used when secret-bearing audit metadata is removed. */
|
|
3
|
+
export declare const MCP_REDACTION_MARKER = "[redacted]";
|
|
4
|
+
/** Return whether a metadata key is likely to contain token or secret data. */
|
|
5
|
+
export declare function isSensitiveMcpMetadataKey(key: string): boolean;
|
|
6
|
+
/** Recursively redact secret-bearing MCP audit metadata before display. */
|
|
7
|
+
export declare function sanitizeMcpAuditMetadata(value: unknown): unknown;
|
|
8
|
+
/** Return an audit event with sanitized metadata. */
|
|
9
|
+
export declare function sanitizeMcpAuditEvent(event: McpAuditEvent): McpAuditEvent;
|
|
10
|
+
/** Return a connection summary with sanitized recent audit events. */
|
|
11
|
+
export declare function sanitizeMcpConnection<S extends string>(connection: McpConnectionSummary<S>): McpConnectionSummary<S>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Marker used when secret-bearing audit metadata is removed. */
|
|
2
|
+
export const MCP_REDACTION_MARKER = "[redacted]";
|
|
3
|
+
const sensitiveKeyMatchers = [
|
|
4
|
+
"authorization",
|
|
5
|
+
"authorization_code",
|
|
6
|
+
"authorizationcode",
|
|
7
|
+
"bearer",
|
|
8
|
+
"code",
|
|
9
|
+
"cookie",
|
|
10
|
+
"headers",
|
|
11
|
+
"secret",
|
|
12
|
+
"token",
|
|
13
|
+
"access_token",
|
|
14
|
+
"accesstoken",
|
|
15
|
+
"refresh_token",
|
|
16
|
+
"refreshtoken",
|
|
17
|
+
"set-cookie"
|
|
18
|
+
];
|
|
19
|
+
/** Return whether a metadata key is likely to contain token or secret data. */
|
|
20
|
+
export function isSensitiveMcpMetadataKey(key) {
|
|
21
|
+
const normalized = key.toLowerCase().replace(/[\s.-]+/g, "_");
|
|
22
|
+
return sensitiveKeyMatchers.some((matcher) => normalized === matcher || normalized.endsWith(`_${matcher}`) || normalized.includes(matcher));
|
|
23
|
+
}
|
|
24
|
+
/** Recursively redact secret-bearing MCP audit metadata before display. */
|
|
25
|
+
export function sanitizeMcpAuditMetadata(value) {
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(sanitizeMcpAuditMetadata);
|
|
28
|
+
if (!value || typeof value !== "object")
|
|
29
|
+
return value;
|
|
30
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
31
|
+
if (isSensitiveMcpMetadataKey(key))
|
|
32
|
+
return [key, MCP_REDACTION_MARKER];
|
|
33
|
+
return [key, sanitizeMcpAuditMetadata(child)];
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/** Return an audit event with sanitized metadata. */
|
|
37
|
+
export function sanitizeMcpAuditEvent(event) {
|
|
38
|
+
return { ...event, metadata: sanitizeMcpAuditMetadata(event.metadata) };
|
|
39
|
+
}
|
|
40
|
+
/** Return a connection summary with sanitized recent audit events. */
|
|
41
|
+
export function sanitizeMcpConnection(connection) {
|
|
42
|
+
return { ...connection, recentAuditEvents: connection.recentAuditEvents.map(sanitizeMcpAuditEvent) };
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { McpScopeValue, McpToolDescriptor } from "./types.js";
|
|
2
|
+
/** Normalize a scope string or array to supported, de-duplicated scopes. */
|
|
3
|
+
export declare function normalizeMcpScopes<S extends McpScopeValue>(value: string | readonly string[] | null | undefined, supportedScopes: readonly S[]): S[];
|
|
4
|
+
/** Format granted scopes for OAuth responses or metadata. */
|
|
5
|
+
export declare function formatMcpScopeString<S extends McpScopeValue>(scopes: readonly S[]): string;
|
|
6
|
+
/** Return whether all required scopes are present in a granted scope set. */
|
|
7
|
+
export declare function hasRequiredMcpScopes<S extends McpScopeValue>(granted: readonly S[], required: readonly S[]): boolean;
|
|
8
|
+
/** Filter tool descriptors to only those allowed by a granted scope set. */
|
|
9
|
+
export declare function filterMcpToolsByScopes<S extends McpScopeValue, T extends McpToolDescriptor<S>>(tools: readonly T[], scopes: readonly S[]): T[];
|
|
10
|
+
/** Resolve requested OAuth scopes, falling back to defaults when none are supplied. */
|
|
11
|
+
export declare function requestedMcpScopes<S extends McpScopeValue>(value: string | null | undefined, allowedScopes: readonly S[], defaultScopes: readonly S[]): S[];
|
|
12
|
+
/** Return whether a raw OAuth scope string includes any unsupported scope. */
|
|
13
|
+
export declare function includesUnsupportedMcpScope<S extends McpScopeValue>(value: string | undefined, allowedScopes: readonly S[]): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Normalize a scope string or array to supported, de-duplicated scopes. */
|
|
2
|
+
export function normalizeMcpScopes(value, supportedScopes) {
|
|
3
|
+
const raw = typeof value === "string" ? value.split(/\s+/) : value ?? [];
|
|
4
|
+
const requested = new Set(raw.map((scope) => scope.trim()).filter(Boolean));
|
|
5
|
+
return supportedScopes.filter((scope, index) => supportedScopes.indexOf(scope) === index && requested.has(scope));
|
|
6
|
+
}
|
|
7
|
+
/** Format granted scopes for OAuth responses or metadata. */
|
|
8
|
+
export function formatMcpScopeString(scopes) {
|
|
9
|
+
return [...new Set(scopes)].join(" ");
|
|
10
|
+
}
|
|
11
|
+
/** Return whether all required scopes are present in a granted scope set. */
|
|
12
|
+
export function hasRequiredMcpScopes(granted, required) {
|
|
13
|
+
const grantedSet = new Set(granted);
|
|
14
|
+
return required.every((scope) => grantedSet.has(scope));
|
|
15
|
+
}
|
|
16
|
+
/** Filter tool descriptors to only those allowed by a granted scope set. */
|
|
17
|
+
export function filterMcpToolsByScopes(tools, scopes) {
|
|
18
|
+
return tools.filter((tool) => hasRequiredMcpScopes(scopes, tool.requiredScopes));
|
|
19
|
+
}
|
|
20
|
+
/** Resolve requested OAuth scopes, falling back to defaults when none are supplied. */
|
|
21
|
+
export function requestedMcpScopes(value, allowedScopes, defaultScopes) {
|
|
22
|
+
const scopes = normalizeMcpScopes(value, allowedScopes);
|
|
23
|
+
return scopes.length > 0 ? scopes : [...defaultScopes];
|
|
24
|
+
}
|
|
25
|
+
/** Return whether a raw OAuth scope string includes any unsupported scope. */
|
|
26
|
+
export function includesUnsupportedMcpScope(value, allowedScopes) {
|
|
27
|
+
if (!value)
|
|
28
|
+
return false;
|
|
29
|
+
const allowed = new Set(allowedScopes);
|
|
30
|
+
return value
|
|
31
|
+
.split(/\s+/)
|
|
32
|
+
.map((scope) => scope.trim())
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.some((scope) => !allowed.has(scope));
|
|
35
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export type McpScopeValue = string;
|
|
2
|
+
export type McpTokenSourceKind = "oauth" | "personal_access_token" | (string & {});
|
|
3
|
+
export type McpActivityKind = "read" | "write" | "management" | (string & {});
|
|
4
|
+
export type McpAuditOutcome = "created" | "updated" | "matched" | "rejected" | "revoked" | "error" | "partial_success" | (string & {});
|
|
5
|
+
/** Product human user shape used in MCP grants, consent, tokens, and audit events. */
|
|
6
|
+
export interface McpUser {
|
|
7
|
+
id: string;
|
|
8
|
+
authProviderKey?: string | null;
|
|
9
|
+
authIssuer?: string | null;
|
|
10
|
+
authSubject?: string | null;
|
|
11
|
+
email: string | null;
|
|
12
|
+
emailVerified?: boolean | null;
|
|
13
|
+
displayName: string | null;
|
|
14
|
+
imageUrl?: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** Tenant/workspace shape used to bind every MCP grant and tool call. */
|
|
17
|
+
export interface McpWorkspace {
|
|
18
|
+
id: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
kind?: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Product-owned membership context for a user inside one workspace. */
|
|
23
|
+
export interface McpWorkspaceMembership {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
role?: string | null;
|
|
27
|
+
capabilities?: readonly string[];
|
|
28
|
+
joinedAt?: string | null;
|
|
29
|
+
lastActiveAt?: string | null;
|
|
30
|
+
}
|
|
31
|
+
/** Signed-in human identity plus the selected workspace for consent/management. */
|
|
32
|
+
export interface McpHumanIdentityContext {
|
|
33
|
+
user: McpUser;
|
|
34
|
+
workspace: McpWorkspace;
|
|
35
|
+
membership: McpWorkspaceMembership;
|
|
36
|
+
sessionFresh?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** User-facing definition of one supported MCP scope. */
|
|
39
|
+
export interface McpScopeDefinition<S extends McpScopeValue = McpScopeValue> {
|
|
40
|
+
scope: S;
|
|
41
|
+
title: string;
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
44
|
+
/** MCP tool annotations mirrored from the MCP SDK shape for portable descriptors. */
|
|
45
|
+
export interface McpToolAnnotations {
|
|
46
|
+
readOnlyHint?: boolean;
|
|
47
|
+
destructiveHint?: boolean;
|
|
48
|
+
idempotentHint?: boolean;
|
|
49
|
+
openWorldHint?: boolean;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
/** Product-neutral descriptor for an MCP tool shown in consent/settings UI. */
|
|
53
|
+
export interface McpToolDescriptor<S extends McpScopeValue = McpScopeValue> {
|
|
54
|
+
name: string;
|
|
55
|
+
title: string;
|
|
56
|
+
description: string;
|
|
57
|
+
requiredScopes: readonly S[];
|
|
58
|
+
activityKind?: McpActivityKind;
|
|
59
|
+
annotations?: McpToolAnnotations;
|
|
60
|
+
}
|
|
61
|
+
/** Product-neutral descriptor for an MCP resource shown in the tool reference. */
|
|
62
|
+
export interface McpResourceDescriptor<S extends McpScopeValue = McpScopeValue> {
|
|
63
|
+
name: string;
|
|
64
|
+
uri: string;
|
|
65
|
+
title: string;
|
|
66
|
+
description: string;
|
|
67
|
+
requiredScopes: readonly S[];
|
|
68
|
+
}
|
|
69
|
+
/** Product-neutral descriptor for an MCP prompt shown in the tool reference. */
|
|
70
|
+
export interface McpPromptDescriptor<S extends McpScopeValue = McpScopeValue> {
|
|
71
|
+
name: string;
|
|
72
|
+
title: string;
|
|
73
|
+
description: string;
|
|
74
|
+
requiredScopes: readonly S[];
|
|
75
|
+
}
|
|
76
|
+
/** Persisted OAuth client registered by a remote MCP client. */
|
|
77
|
+
export interface OAuthClientRecord<S extends McpScopeValue = McpScopeValue> {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
displayName: string;
|
|
81
|
+
redirectUris: readonly string[];
|
|
82
|
+
scopes: readonly S[];
|
|
83
|
+
createdAt: string;
|
|
84
|
+
revokedAt?: string | null;
|
|
85
|
+
revokedByUserId?: string | null;
|
|
86
|
+
revokedReason?: string | null;
|
|
87
|
+
}
|
|
88
|
+
/** Persisted, hashed, single-use authorization code created after consent. */
|
|
89
|
+
export interface OAuthAuthorizationCodeRecord<S extends McpScopeValue = McpScopeValue> {
|
|
90
|
+
id: string;
|
|
91
|
+
codeHash: string;
|
|
92
|
+
clientId: string;
|
|
93
|
+
redirectUri: string;
|
|
94
|
+
workspaceId: string;
|
|
95
|
+
userId: string;
|
|
96
|
+
scopes: readonly S[];
|
|
97
|
+
resource: string;
|
|
98
|
+
codeChallenge: string;
|
|
99
|
+
codeChallengeMethod: "S256";
|
|
100
|
+
expiresAt: string;
|
|
101
|
+
createdAt: string;
|
|
102
|
+
consumedAt: string | null;
|
|
103
|
+
}
|
|
104
|
+
/** Persisted, hashed MCP access or refresh token. */
|
|
105
|
+
export interface OAuthTokenRecord<S extends McpScopeValue = McpScopeValue> {
|
|
106
|
+
id: string;
|
|
107
|
+
tokenHash: string;
|
|
108
|
+
kind: "access" | "refresh";
|
|
109
|
+
grantId: string;
|
|
110
|
+
clientId: string;
|
|
111
|
+
workspaceId: string;
|
|
112
|
+
userId: string;
|
|
113
|
+
scopes: readonly S[];
|
|
114
|
+
resource: string;
|
|
115
|
+
sourceKind: McpTokenSourceKind;
|
|
116
|
+
displayName: string | null;
|
|
117
|
+
expiresAt: string;
|
|
118
|
+
createdAt: string;
|
|
119
|
+
lastUsedAt: string | null;
|
|
120
|
+
revokedAt: string | null;
|
|
121
|
+
revokedByUserId: string | null;
|
|
122
|
+
revokedReason: string | null;
|
|
123
|
+
}
|
|
124
|
+
/** Product session record for initialized Streamable HTTP MCP sessions. */
|
|
125
|
+
export interface McpSessionRecord<S extends McpScopeValue = McpScopeValue> {
|
|
126
|
+
id: string;
|
|
127
|
+
accessTokenId: string;
|
|
128
|
+
clientId: string;
|
|
129
|
+
workspaceId: string;
|
|
130
|
+
userId: string;
|
|
131
|
+
scopes: readonly S[];
|
|
132
|
+
protocolVersion: string | null;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
lastSeenAt: string;
|
|
135
|
+
closedAt: string | null;
|
|
136
|
+
}
|
|
137
|
+
/** Auditable externally authorized MCP activity. */
|
|
138
|
+
export interface McpAuditEvent {
|
|
139
|
+
id: string;
|
|
140
|
+
workspaceId: string;
|
|
141
|
+
userId: string;
|
|
142
|
+
clientId: string;
|
|
143
|
+
accessTokenId: string | null;
|
|
144
|
+
sourceKind: McpTokenSourceKind;
|
|
145
|
+
activityKind: McpActivityKind;
|
|
146
|
+
toolName: string;
|
|
147
|
+
targetRecordIds: string[];
|
|
148
|
+
outcome: McpAuditOutcome;
|
|
149
|
+
metadata: Record<string, unknown>;
|
|
150
|
+
createdAt: string;
|
|
151
|
+
}
|
|
152
|
+
/** Settings-page summary of one OAuth connection/grant. */
|
|
153
|
+
export interface McpConnectionSummary<S extends McpScopeValue = McpScopeValue> {
|
|
154
|
+
grantId: string;
|
|
155
|
+
workspaceId: string;
|
|
156
|
+
userId: string;
|
|
157
|
+
userEmail: string | null;
|
|
158
|
+
clientId: string;
|
|
159
|
+
clientName: string;
|
|
160
|
+
redirectOrigin: string | null;
|
|
161
|
+
redirectUris: string[];
|
|
162
|
+
scopes: S[];
|
|
163
|
+
resource: string;
|
|
164
|
+
tokenSourceKind: "oauth";
|
|
165
|
+
createdAt: string;
|
|
166
|
+
expiresAt: string | null;
|
|
167
|
+
lastUsedAt: string | null;
|
|
168
|
+
revokedAt: string | null;
|
|
169
|
+
revokedByUserId: string | null;
|
|
170
|
+
activeSessionCount: number;
|
|
171
|
+
tokenCount: number;
|
|
172
|
+
recentAuditEvents: McpAuditEvent[];
|
|
173
|
+
}
|
|
174
|
+
/** Settings-page summary of one manually created developer token. */
|
|
175
|
+
export interface McpDeveloperTokenSummary<S extends McpScopeValue = McpScopeValue> {
|
|
176
|
+
tokenId: string;
|
|
177
|
+
workspaceId: string;
|
|
178
|
+
userId: string;
|
|
179
|
+
userEmail: string | null;
|
|
180
|
+
name: string;
|
|
181
|
+
scopes: S[];
|
|
182
|
+
resource: string;
|
|
183
|
+
sourceKind: "personal_access_token";
|
|
184
|
+
createdAt: string;
|
|
185
|
+
expiresAt: string;
|
|
186
|
+
lastUsedAt: string | null;
|
|
187
|
+
revokedAt: string | null;
|
|
188
|
+
revokedByUserId: string | null;
|
|
189
|
+
expired: boolean;
|
|
190
|
+
}
|
|
191
|
+
/** Connector details displayed to users and advertised to MCP clients. */
|
|
192
|
+
export interface McpConnectorConfig<S extends McpScopeValue = McpScopeValue> {
|
|
193
|
+
connectorUrl: string;
|
|
194
|
+
appBaseUrl: string;
|
|
195
|
+
resourceUri: string;
|
|
196
|
+
publicHttpsReady: boolean;
|
|
197
|
+
scopes: McpScopeDefinition<S>[];
|
|
198
|
+
tools: McpToolDescriptor<S>[];
|
|
199
|
+
}
|
|
200
|
+
/** Combined connector details for a workspace settings page. */
|
|
201
|
+
export interface McpConnectorDetails<S extends McpScopeValue = McpScopeValue> {
|
|
202
|
+
workspace: McpWorkspace;
|
|
203
|
+
user: McpUser;
|
|
204
|
+
connector: McpConnectorConfig<S>;
|
|
205
|
+
}
|
|
206
|
+
/** Server-side filters accepted by the shared audit-event management endpoint. */
|
|
207
|
+
export interface McpAuditEventFilters {
|
|
208
|
+
userId?: string;
|
|
209
|
+
clientId?: string;
|
|
210
|
+
accessTokenId?: string;
|
|
211
|
+
sourceKind?: McpTokenSourceKind;
|
|
212
|
+
toolName?: string;
|
|
213
|
+
outcome?: McpAuditOutcome;
|
|
214
|
+
activityKind?: McpActivityKind;
|
|
215
|
+
limit?: number;
|
|
216
|
+
}
|
|
217
|
+
/** Request body for creating a developer token. */
|
|
218
|
+
export interface McpCreateDeveloperTokenInput<S extends McpScopeValue = McpScopeValue> {
|
|
219
|
+
userId?: string;
|
|
220
|
+
name: string;
|
|
221
|
+
scopes: readonly S[];
|
|
222
|
+
expiresAt?: string;
|
|
223
|
+
expiresInDays?: number;
|
|
224
|
+
}
|
|
225
|
+
/** Developer-token create response; rawToken must be displayed once and not stored. */
|
|
226
|
+
export interface McpCreateDeveloperTokenResult<S extends McpScopeValue = McpScopeValue> {
|
|
227
|
+
token: McpDeveloperTokenSummary<S>;
|
|
228
|
+
rawToken: string;
|
|
229
|
+
}
|
|
230
|
+
/** Response body for listing OAuth connections. */
|
|
231
|
+
export interface McpConnectionListResponse<S extends McpScopeValue = McpScopeValue> {
|
|
232
|
+
connections: McpConnectionSummary<S>[];
|
|
233
|
+
}
|
|
234
|
+
/** Response body for loading one OAuth connection. */
|
|
235
|
+
export interface McpConnectionDetailResponse<S extends McpScopeValue = McpScopeValue> {
|
|
236
|
+
connection: McpConnectionSummary<S>;
|
|
237
|
+
}
|
|
238
|
+
/** Response body for listing developer tokens. */
|
|
239
|
+
export interface McpDeveloperTokenListResponse<S extends McpScopeValue = McpScopeValue> {
|
|
240
|
+
tokens: McpDeveloperTokenSummary<S>[];
|
|
241
|
+
}
|
|
242
|
+
/** Response body for creating a developer token. */
|
|
243
|
+
export interface McpDeveloperTokenCreateResponse<S extends McpScopeValue = McpScopeValue> extends McpCreateDeveloperTokenResult<S> {
|
|
244
|
+
}
|
|
245
|
+
/** Response body for revoking a developer token. */
|
|
246
|
+
export interface McpDeveloperTokenRevokeResponse<S extends McpScopeValue = McpScopeValue> {
|
|
247
|
+
token: McpDeveloperTokenSummary<S>;
|
|
248
|
+
}
|
|
249
|
+
/** Response body for revoking an OAuth connection. */
|
|
250
|
+
export interface McpConnectionRevokeResponse<S extends McpScopeValue = McpScopeValue> {
|
|
251
|
+
connection: McpConnectionSummary<S>;
|
|
252
|
+
}
|
|
253
|
+
/** Response body for listing audit events. */
|
|
254
|
+
export interface McpAuditEventListResponse {
|
|
255
|
+
events: McpAuditEvent[];
|
|
256
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|