@opengeni/capabilities 0.2.0 → 0.3.1-canary.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 +17 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +251 -23
- package/dist/index.js.map +1 -1
- package/dist/integration-definitions.d.ts +0 -1
- package/dist/integration-presentations.d.ts +32 -0
- package/dist/mcp-bridge.d.ts +41 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
- package/src/graphql.ts +16 -0
- package/src/index.ts +2 -0
- package/src/integration-definitions.ts +24 -38
- package/src/integration-presentations.ts +185 -0
- package/src/mcp-bridge.ts +117 -0
- package/src/openapi.ts +16 -0
- package/src/types.ts +2 -0
|
@@ -43,7 +43,6 @@ export interface IntegrationFacetDefinition {
|
|
|
43
43
|
readonly capabilities: Readonly<Record<string, unknown>>;
|
|
44
44
|
}
|
|
45
45
|
export declare const GOOGLE_DRIVE_INTEGRATION_DEFINITION: IntegrationDefinition;
|
|
46
|
-
export declare const GOOGLE_GMAIL_INTEGRATION_DEFINITION: IntegrationDefinition;
|
|
47
46
|
export declare const MICROSOFT_GRAPH_OPENAPI_URL = "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml";
|
|
48
47
|
export declare const MICROSOFT_GRAPH_BASE_URL = "https://graph.microsoft.com/v1.0";
|
|
49
48
|
export declare const MICROSOFT_OUTLOOK_MAIL_INTEGRATION_DEFINITION: IntegrationDefinition;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewed consent copy for the core API integration definitions.
|
|
3
|
+
*
|
|
4
|
+
* Presentation only: nothing here grants a scope, selects a connection, or
|
|
5
|
+
* replaces server-side authorization. The copy used to live hardcoded in the
|
|
6
|
+
* web bundle (`REVIEWED_INTEGRATION_EXPERIENCES`); it is served with the
|
|
7
|
+
* definition now so polishing a consent screen is a data change, not a
|
|
8
|
+
* frontend release. The web keeps its generic fallback for any definition or
|
|
9
|
+
* field missing here. Gmail's reviewed presentation lives on its catalog row
|
|
10
|
+
* instead (`data/catalog/curated.json`): Gmail is a Connector, not one of
|
|
11
|
+
* these API integration definitions.
|
|
12
|
+
*
|
|
13
|
+
* MCP connectors carry the same shape on their curated catalog row
|
|
14
|
+
* (`presentation` in `data/catalog/curated.json` -> importer ->
|
|
15
|
+
* `capability_catalog_items.metadata.presentation`).
|
|
16
|
+
*/
|
|
17
|
+
export type IntegrationPresentationIcon = "calendar" | "cloud" | "contacts" | "files" | "mail";
|
|
18
|
+
export type IntegrationPresentationCopy = {
|
|
19
|
+
readonly providerName?: string;
|
|
20
|
+
readonly icon?: IntegrationPresentationIcon;
|
|
21
|
+
readonly introduction?: string;
|
|
22
|
+
readonly capabilities?: readonly {
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly description: string;
|
|
25
|
+
}[];
|
|
26
|
+
readonly permissionSummary?: string;
|
|
27
|
+
readonly scopeLabels?: Readonly<Record<string, {
|
|
28
|
+
readonly label: string;
|
|
29
|
+
readonly description: string;
|
|
30
|
+
}>>;
|
|
31
|
+
};
|
|
32
|
+
export declare const INTEGRATION_DEFINITION_PRESENTATIONS: Readonly<Record<string, IntegrationPresentationCopy>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MCPServer } from "@openai/agents";
|
|
2
|
+
export declare const LOCAL_MCP_BRIDGE_CONTRACT_VERSION: 1;
|
|
3
|
+
export type LocalMcpBridgeAuthority = "connection" | "host" | "none";
|
|
4
|
+
export type LocalMcpBridgeToolSurface = "static_reviewed";
|
|
5
|
+
export type LocalMcpBridgeDestination = Readonly<{
|
|
6
|
+
origin: string;
|
|
7
|
+
pathPrefix: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Secret-free description of an in-process provider-to-MCP adapter.
|
|
11
|
+
*
|
|
12
|
+
* This is observability and registration metadata, not authorization. The
|
|
13
|
+
* adapter must still revalidate its named authority before each physical
|
|
14
|
+
* provider request and keep credentials outside tool results and schemas.
|
|
15
|
+
*/
|
|
16
|
+
export type LocalMcpBridgeDescriptor = Readonly<{
|
|
17
|
+
contractVersion: typeof LOCAL_MCP_BRIDGE_CONTRACT_VERSION;
|
|
18
|
+
adapterId: string;
|
|
19
|
+
providerId: string;
|
|
20
|
+
catalogIdentity: string;
|
|
21
|
+
transport: "in_process";
|
|
22
|
+
authority: LocalMcpBridgeAuthority;
|
|
23
|
+
toolSurface: LocalMcpBridgeToolSurface;
|
|
24
|
+
mutationReplay: "safe_reads_only";
|
|
25
|
+
destinations: readonly LocalMcpBridgeDestination[];
|
|
26
|
+
}>;
|
|
27
|
+
export interface LocalMcpBridgeServer extends MCPServer {
|
|
28
|
+
readonly bridge: LocalMcpBridgeDescriptor;
|
|
29
|
+
}
|
|
30
|
+
export interface LocalMcpBridgeAdapter<TConfig, TContext> {
|
|
31
|
+
readonly adapterId: string;
|
|
32
|
+
matches(config: TConfig): boolean;
|
|
33
|
+
create(config: TConfig, context: TContext): LocalMcpBridgeServer;
|
|
34
|
+
}
|
|
35
|
+
export declare function defineLocalMcpBridgeDescriptor(input: Omit<LocalMcpBridgeDescriptor, "contractVersion" | "transport">): LocalMcpBridgeDescriptor;
|
|
36
|
+
export declare function isLocalMcpBridgeServer(server: MCPServer): server is LocalMcpBridgeServer;
|
|
37
|
+
/**
|
|
38
|
+
* Select exactly one adapter for a runtime catalog row. Ambiguous matches fail
|
|
39
|
+
* closed so adding a bridge cannot silently replace another provider route.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createLocalMcpBridgeFromAdapters<TConfig, TContext>(adapters: readonly LocalMcpBridgeAdapter<TConfig, TContext>[], config: TConfig, context: TContext): LocalMcpBridgeServer | null;
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface IntegrationCredentialAudience {
|
|
|
30
30
|
export interface ResolvedIntegrationCredential {
|
|
31
31
|
readonly audience: IntegrationCredentialAudience;
|
|
32
32
|
readonly placements: readonly IntegrationCredentialPlacement[];
|
|
33
|
+
/** Exact accepted-attempt fence invoked immediately before one HTTP request. */
|
|
34
|
+
readonly authorizeProviderRequest?: () => Promise<boolean>;
|
|
33
35
|
readonly expiresAt?: string;
|
|
34
36
|
readonly scope?: readonly string[];
|
|
35
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/capabilities",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1-canary.0",
|
|
4
4
|
"description": "Protocol-neutral capability integration compilers and local MCP adapters for OpenGeni.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@openai/agents": "0.14.3",
|
|
39
|
-
"@opengeni/network": "^0.2.
|
|
39
|
+
"@opengeni/network": "^0.2.3-canary.0",
|
|
40
40
|
"graphql": "^16.12.0",
|
|
41
41
|
"js-yaml": "4.1.1"
|
|
42
42
|
},
|
package/src/graphql.ts
CHANGED
|
@@ -428,6 +428,22 @@ async function sendGraphqlRequest(
|
|
|
428
428
|
headers.set("accept", "application/json");
|
|
429
429
|
headers.set("content-type", "application/json");
|
|
430
430
|
if (credential) applyCredentialPlacements(endpoint, headers, credential);
|
|
431
|
+
if (credential?.authorizeProviderRequest) {
|
|
432
|
+
let authorized = false;
|
|
433
|
+
try {
|
|
434
|
+
authorized = await credential.authorizeProviderRequest();
|
|
435
|
+
} catch {
|
|
436
|
+
authorized = false;
|
|
437
|
+
}
|
|
438
|
+
if (!authorized) {
|
|
439
|
+
throw new IntegrationInvocationError(
|
|
440
|
+
"authorization_rejected",
|
|
441
|
+
"The connected account is no longer authorized for this operation",
|
|
442
|
+
"not_started",
|
|
443
|
+
false,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
431
447
|
return await fetchWithDeadline(
|
|
432
448
|
options.transport,
|
|
433
449
|
endpoint,
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,9 @@ export * from "./auth";
|
|
|
2
2
|
export * from "./graphql";
|
|
3
3
|
export * from "./http";
|
|
4
4
|
export * from "./mcp-manifest";
|
|
5
|
+
export * from "./mcp-bridge";
|
|
5
6
|
export * from "./openapi";
|
|
6
7
|
export * from "./integration-definitions";
|
|
8
|
+
export * from "./integration-presentations";
|
|
7
9
|
export * from "./revision";
|
|
8
10
|
export * from "./types";
|
|
@@ -123,7 +123,7 @@ const driveKnowledgeFacet = (
|
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
const mailboxFacets = (
|
|
126
|
-
provider: "
|
|
126
|
+
provider: "microsoft-outlook-mail",
|
|
127
127
|
): readonly IntegrationFacetDefinition[] => [
|
|
128
128
|
{
|
|
129
129
|
facetKey: "mail-inbox",
|
|
@@ -140,7 +140,7 @@ const mailboxFacets = (
|
|
|
140
140
|
provider,
|
|
141
141
|
connectionRequired: true,
|
|
142
142
|
delivery: "poll",
|
|
143
|
-
cursor:
|
|
143
|
+
cursor: "delta_link",
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
146
|
{
|
|
@@ -160,7 +160,7 @@ const mailboxFacets = (
|
|
|
160
160
|
delivery: "email",
|
|
161
161
|
},
|
|
162
162
|
},
|
|
163
|
-
accountIdentityFacet(
|
|
163
|
+
accountIdentityFacet("microsoft"),
|
|
164
164
|
];
|
|
165
165
|
|
|
166
166
|
const googleDiscoveryUrl = (service: string, version: string): string =>
|
|
@@ -191,22 +191,6 @@ export const GOOGLE_DRIVE_INTEGRATION_DEFINITION: IntegrationDefinition = {
|
|
|
191
191
|
facets: [driveKnowledgeFacet("google-drive"), accountIdentityFacet("google")],
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
export const GOOGLE_GMAIL_INTEGRATION_DEFINITION: IntegrationDefinition = {
|
|
195
|
-
id: "google-gmail",
|
|
196
|
-
name: "Gmail",
|
|
197
|
-
summary: "Messages, threads, labels, drafts, and sending mail.",
|
|
198
|
-
protocol: "openapi",
|
|
199
|
-
provider: { id: "google", domain: "gmail.googleapis.com" },
|
|
200
|
-
source: { kind: "google_discovery", url: googleDiscoveryUrl("gmail", "v1") },
|
|
201
|
-
baseUrl: "https://gmail.googleapis.com/",
|
|
202
|
-
authentication: googleOAuth(["https://mail.google.com/"]),
|
|
203
|
-
healthCheck: {
|
|
204
|
-
operationKey: "gmail.users.labels.list",
|
|
205
|
-
arguments: { path: { userId: "me" } },
|
|
206
|
-
},
|
|
207
|
-
facets: mailboxFacets("google-gmail"),
|
|
208
|
-
};
|
|
209
|
-
|
|
210
194
|
export const MICROSOFT_GRAPH_OPENAPI_URL =
|
|
211
195
|
"https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml";
|
|
212
196
|
export const MICROSOFT_GRAPH_BASE_URL = "https://graph.microsoft.com/v1.0";
|
|
@@ -338,7 +322,6 @@ export const MICROSOFT_ONEDRIVE_INTEGRATION_DEFINITION: IntegrationDefinition =
|
|
|
338
322
|
|
|
339
323
|
export const CORE_INTEGRATION_DEFINITIONS: readonly IntegrationDefinition[] = [
|
|
340
324
|
GOOGLE_DRIVE_INTEGRATION_DEFINITION,
|
|
341
|
-
GOOGLE_GMAIL_INTEGRATION_DEFINITION,
|
|
342
325
|
MICROSOFT_OUTLOOK_MAIL_INTEGRATION_DEFINITION,
|
|
343
326
|
MICROSOFT_OUTLOOK_CALENDAR_INTEGRATION_DEFINITION,
|
|
344
327
|
MICROSOFT_OUTLOOK_CONTACTS_INTEGRATION_DEFINITION,
|
|
@@ -480,23 +463,23 @@ function collectGoogleMethods(
|
|
|
480
463
|
const path = stringValue(rawMethod.path);
|
|
481
464
|
const httpMethod = stringValue(rawMethod.httpMethod)?.toLowerCase();
|
|
482
465
|
if (!path || !httpMethod) continue;
|
|
483
|
-
const parameters = Object.entries(
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
466
|
+
const parameters = Object.entries(isRecord(rawMethod.parameters) ? rawMethod.parameters : {})
|
|
467
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
468
|
+
.flatMap(([name, rawParameter]): Record<string, unknown>[] => {
|
|
469
|
+
if (!isRecord(rawParameter)) return [];
|
|
470
|
+
const location = rawParameter.location === "path" ? "path" : "query";
|
|
471
|
+
return [
|
|
472
|
+
{
|
|
473
|
+
name,
|
|
474
|
+
in: location,
|
|
475
|
+
required: location === "path" || rawParameter.required === true,
|
|
476
|
+
...(stringValue(rawParameter.description)
|
|
477
|
+
? { description: stringValue(rawParameter.description) }
|
|
478
|
+
: {}),
|
|
479
|
+
schema: convertGoogleSchema(rawParameter),
|
|
480
|
+
},
|
|
481
|
+
];
|
|
482
|
+
});
|
|
500
483
|
const requestRef = isRecord(rawMethod.request)
|
|
501
484
|
? stringValue(rawMethod.request.$ref)
|
|
502
485
|
: undefined;
|
|
@@ -505,7 +488,10 @@ function collectGoogleMethods(
|
|
|
505
488
|
: undefined;
|
|
506
489
|
const operation: Record<string, unknown> = {
|
|
507
490
|
operationId: stringValue(rawMethod.id) ?? fallbackId,
|
|
508
|
-
|
|
491
|
+
// Discovery descriptions are often full documentation paragraphs. Keep
|
|
492
|
+
// them as descriptions and use the stable method identity for the short
|
|
493
|
+
// OpenGeni tool display name.
|
|
494
|
+
summary: stringValue(rawMethod.id) ?? fallbackId,
|
|
509
495
|
description: stringValue(rawMethod.description),
|
|
510
496
|
parameters,
|
|
511
497
|
responses: {
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewed consent copy for the core API integration definitions.
|
|
3
|
+
*
|
|
4
|
+
* Presentation only: nothing here grants a scope, selects a connection, or
|
|
5
|
+
* replaces server-side authorization. The copy used to live hardcoded in the
|
|
6
|
+
* web bundle (`REVIEWED_INTEGRATION_EXPERIENCES`); it is served with the
|
|
7
|
+
* definition now so polishing a consent screen is a data change, not a
|
|
8
|
+
* frontend release. The web keeps its generic fallback for any definition or
|
|
9
|
+
* field missing here. Gmail's reviewed presentation lives on its catalog row
|
|
10
|
+
* instead (`data/catalog/curated.json`): Gmail is a Connector, not one of
|
|
11
|
+
* these API integration definitions.
|
|
12
|
+
*
|
|
13
|
+
* MCP connectors carry the same shape on their curated catalog row
|
|
14
|
+
* (`presentation` in `data/catalog/curated.json` -> importer ->
|
|
15
|
+
* `capability_catalog_items.metadata.presentation`).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type IntegrationPresentationIcon = "calendar" | "cloud" | "contacts" | "files" | "mail";
|
|
19
|
+
|
|
20
|
+
export type IntegrationPresentationCopy = {
|
|
21
|
+
readonly providerName?: string;
|
|
22
|
+
readonly icon?: IntegrationPresentationIcon;
|
|
23
|
+
readonly introduction?: string;
|
|
24
|
+
readonly capabilities?: readonly { readonly title: string; readonly description: string }[];
|
|
25
|
+
readonly permissionSummary?: string;
|
|
26
|
+
readonly scopeLabels?: Readonly<
|
|
27
|
+
Record<string, { readonly label: string; readonly description: string }>
|
|
28
|
+
>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const INTEGRATION_DEFINITION_PRESENTATIONS: Readonly<
|
|
32
|
+
Record<string, IntegrationPresentationCopy>
|
|
33
|
+
> = {
|
|
34
|
+
"google-drive": {
|
|
35
|
+
providerName: "Google",
|
|
36
|
+
icon: "files",
|
|
37
|
+
introduction: "Let agents work with files in the Google Drive account you choose.",
|
|
38
|
+
capabilities: [
|
|
39
|
+
{
|
|
40
|
+
title: "Find files and folders",
|
|
41
|
+
description: "Browse and search content in My Drive and shared drives.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: "Create and update content",
|
|
45
|
+
description: "Work with files and folders through the reviewed Drive tools.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: "Manage sharing",
|
|
49
|
+
description: "Review and update links, permissions, and shared-drive content.",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
permissionSummary:
|
|
53
|
+
"Google asks for access to the Drive account you approve, including files shared with that account.",
|
|
54
|
+
scopeLabels: {
|
|
55
|
+
"https://www.googleapis.com/auth/drive": {
|
|
56
|
+
label: "Work with Google Drive files",
|
|
57
|
+
description: "See, create, edit, organize, and share files available to this account.",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
"microsoft-outlook-mail": {
|
|
62
|
+
providerName: "Microsoft",
|
|
63
|
+
icon: "mail",
|
|
64
|
+
introduction: "Let agents work with mail in the Microsoft account you choose.",
|
|
65
|
+
capabilities: [
|
|
66
|
+
{
|
|
67
|
+
title: "Find and understand mail",
|
|
68
|
+
description: "Search messages, folders, and attachments for useful context.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: "Draft and send messages",
|
|
72
|
+
description: "Prepare, update, and send mail through the reviewed Outlook tools.",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
title: "Manage mailbox settings",
|
|
76
|
+
description: "Work with supported folders, classifications, and mailbox preferences.",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
permissionSummary:
|
|
80
|
+
"Microsoft asks for mail and mailbox-setting access for the account you approve.",
|
|
81
|
+
scopeLabels: {
|
|
82
|
+
"Mail.ReadWrite": {
|
|
83
|
+
label: "Read and update mail",
|
|
84
|
+
description: "Work with messages, folders, and attachments in this mailbox.",
|
|
85
|
+
},
|
|
86
|
+
"Mail.Send": {
|
|
87
|
+
label: "Send mail",
|
|
88
|
+
description: "Send messages as the connected Microsoft account.",
|
|
89
|
+
},
|
|
90
|
+
"MailboxSettings.ReadWrite": {
|
|
91
|
+
label: "Manage mailbox settings",
|
|
92
|
+
description: "Read and update supported Outlook mailbox preferences.",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
"microsoft-outlook-calendar": {
|
|
97
|
+
providerName: "Microsoft",
|
|
98
|
+
icon: "calendar",
|
|
99
|
+
introduction: "Let agents help coordinate the calendars in your Microsoft account.",
|
|
100
|
+
capabilities: [
|
|
101
|
+
{
|
|
102
|
+
title: "Understand your schedule",
|
|
103
|
+
description: "Review calendars, events, availability, and reminders.",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: "Plan meetings",
|
|
107
|
+
description: "Find suitable times and coordinate calendar activity.",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
title: "Manage events",
|
|
111
|
+
description: "Create and update events through the reviewed calendar tools.",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
permissionSummary:
|
|
115
|
+
"Microsoft asks for permission to view and manage calendars for the account you approve.",
|
|
116
|
+
scopeLabels: {
|
|
117
|
+
"Calendars.ReadWrite": {
|
|
118
|
+
label: "View and manage calendars",
|
|
119
|
+
description: "Read, create, update, and organize calendar events.",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
"microsoft-outlook-contacts": {
|
|
124
|
+
providerName: "Microsoft",
|
|
125
|
+
icon: "contacts",
|
|
126
|
+
introduction: "Let agents work with contacts in your Microsoft account.",
|
|
127
|
+
capabilities: [
|
|
128
|
+
{
|
|
129
|
+
title: "Find people",
|
|
130
|
+
description: "Look up contacts and relevant people suggestions.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: "Organize contacts",
|
|
134
|
+
description: "Work with contacts and contact folders.",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: "Keep details current",
|
|
138
|
+
description: "Create or update contact information through reviewed tools.",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
permissionSummary:
|
|
142
|
+
"Microsoft asks for contact access and people suggestions for the account you approve.",
|
|
143
|
+
scopeLabels: {
|
|
144
|
+
"Contacts.ReadWrite": {
|
|
145
|
+
label: "View and manage contacts",
|
|
146
|
+
description: "Read, create, update, and organize contacts and contact folders.",
|
|
147
|
+
},
|
|
148
|
+
"People.Read.All": {
|
|
149
|
+
label: "Find relevant people",
|
|
150
|
+
description: "Use people suggestions available to the connected account.",
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
"microsoft-onedrive": {
|
|
155
|
+
providerName: "Microsoft",
|
|
156
|
+
icon: "cloud",
|
|
157
|
+
introduction: "Let agents work with files in the Microsoft account you choose.",
|
|
158
|
+
capabilities: [
|
|
159
|
+
{
|
|
160
|
+
title: "Find files and folders",
|
|
161
|
+
description: "Browse drives, folders, shared items, and sites available to the account.",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
title: "Create and update content",
|
|
165
|
+
description: "Work with OneDrive and SharePoint files through reviewed tools.",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
title: "Manage sharing",
|
|
169
|
+
description: "Review and update sharing links and permissions.",
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
permissionSummary:
|
|
173
|
+
"Microsoft asks for file and site access anywhere the connected account already has access.",
|
|
174
|
+
scopeLabels: {
|
|
175
|
+
"Files.ReadWrite.All": {
|
|
176
|
+
label: "Work with accessible files",
|
|
177
|
+
description: "Read, create, update, and organize files available to this account.",
|
|
178
|
+
},
|
|
179
|
+
"Sites.ReadWrite.All": {
|
|
180
|
+
label: "Work with accessible sites",
|
|
181
|
+
description: "Read and update files in SharePoint sites available to this account.",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { MCPServer } from "@openai/agents";
|
|
2
|
+
|
|
3
|
+
export const LOCAL_MCP_BRIDGE_CONTRACT_VERSION = 1 as const;
|
|
4
|
+
|
|
5
|
+
export type LocalMcpBridgeAuthority = "connection" | "host" | "none";
|
|
6
|
+
export type LocalMcpBridgeToolSurface = "static_reviewed";
|
|
7
|
+
|
|
8
|
+
export type LocalMcpBridgeDestination = Readonly<{
|
|
9
|
+
origin: string;
|
|
10
|
+
pathPrefix: string;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Secret-free description of an in-process provider-to-MCP adapter.
|
|
15
|
+
*
|
|
16
|
+
* This is observability and registration metadata, not authorization. The
|
|
17
|
+
* adapter must still revalidate its named authority before each physical
|
|
18
|
+
* provider request and keep credentials outside tool results and schemas.
|
|
19
|
+
*/
|
|
20
|
+
export type LocalMcpBridgeDescriptor = Readonly<{
|
|
21
|
+
contractVersion: typeof LOCAL_MCP_BRIDGE_CONTRACT_VERSION;
|
|
22
|
+
adapterId: string;
|
|
23
|
+
providerId: string;
|
|
24
|
+
catalogIdentity: string;
|
|
25
|
+
transport: "in_process";
|
|
26
|
+
authority: LocalMcpBridgeAuthority;
|
|
27
|
+
toolSurface: LocalMcpBridgeToolSurface;
|
|
28
|
+
mutationReplay: "safe_reads_only";
|
|
29
|
+
destinations: readonly LocalMcpBridgeDestination[];
|
|
30
|
+
}>;
|
|
31
|
+
|
|
32
|
+
export interface LocalMcpBridgeServer extends MCPServer {
|
|
33
|
+
readonly bridge: LocalMcpBridgeDescriptor;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LocalMcpBridgeAdapter<TConfig, TContext> {
|
|
37
|
+
readonly adapterId: string;
|
|
38
|
+
matches(config: TConfig): boolean;
|
|
39
|
+
create(config: TConfig, context: TContext): LocalMcpBridgeServer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function defineLocalMcpBridgeDescriptor(
|
|
43
|
+
input: Omit<LocalMcpBridgeDescriptor, "contractVersion" | "transport">,
|
|
44
|
+
): LocalMcpBridgeDescriptor {
|
|
45
|
+
const adapterId = boundedIdentity(input.adapterId, "adapterId");
|
|
46
|
+
const providerId = boundedIdentity(input.providerId, "providerId");
|
|
47
|
+
const catalogIdentity = boundedIdentity(input.catalogIdentity, "catalogIdentity", 512);
|
|
48
|
+
if (input.destinations.length === 0 || input.destinations.length > 32) {
|
|
49
|
+
throw new Error("Local MCP bridge must declare 1-32 provider destinations");
|
|
50
|
+
}
|
|
51
|
+
const destinations = input.destinations.map((destination) => {
|
|
52
|
+
const url = new URL(destination.origin);
|
|
53
|
+
if (url.protocol !== "https:" || url.origin !== destination.origin) {
|
|
54
|
+
throw new Error("Local MCP bridge destinations must be exact HTTPS origins");
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
!destination.pathPrefix.startsWith("/") ||
|
|
58
|
+
destination.pathPrefix.includes("\\") ||
|
|
59
|
+
destination.pathPrefix.includes("?") ||
|
|
60
|
+
destination.pathPrefix.includes("#") ||
|
|
61
|
+
new URL(destination.pathPrefix, url.origin).pathname !== destination.pathPrefix
|
|
62
|
+
) {
|
|
63
|
+
throw new Error("Local MCP bridge destination pathPrefix must be an absolute URL path");
|
|
64
|
+
}
|
|
65
|
+
return Object.freeze({ origin: url.origin, pathPrefix: destination.pathPrefix });
|
|
66
|
+
});
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
contractVersion: LOCAL_MCP_BRIDGE_CONTRACT_VERSION,
|
|
69
|
+
adapterId,
|
|
70
|
+
providerId,
|
|
71
|
+
catalogIdentity,
|
|
72
|
+
transport: "in_process",
|
|
73
|
+
authority: input.authority,
|
|
74
|
+
toolSurface: input.toolSurface,
|
|
75
|
+
mutationReplay: input.mutationReplay,
|
|
76
|
+
destinations: Object.freeze(destinations),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isLocalMcpBridgeServer(server: MCPServer): server is LocalMcpBridgeServer {
|
|
81
|
+
const bridge = (server as Partial<LocalMcpBridgeServer>).bridge;
|
|
82
|
+
return (
|
|
83
|
+
bridge?.contractVersion === LOCAL_MCP_BRIDGE_CONTRACT_VERSION &&
|
|
84
|
+
bridge.transport === "in_process"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Select exactly one adapter for a runtime catalog row. Ambiguous matches fail
|
|
90
|
+
* closed so adding a bridge cannot silently replace another provider route.
|
|
91
|
+
*/
|
|
92
|
+
export function createLocalMcpBridgeFromAdapters<TConfig, TContext>(
|
|
93
|
+
adapters: readonly LocalMcpBridgeAdapter<TConfig, TContext>[],
|
|
94
|
+
config: TConfig,
|
|
95
|
+
context: TContext,
|
|
96
|
+
): LocalMcpBridgeServer | null {
|
|
97
|
+
const matches = adapters.filter((adapter) => adapter.matches(config));
|
|
98
|
+
if (matches.length === 0) return null;
|
|
99
|
+
if (matches.length > 1) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Multiple local MCP bridge adapters matched: ${matches.map((entry) => entry.adapterId).join(", ")}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const adapter = matches[0]!;
|
|
105
|
+
const server = adapter.create(config, context);
|
|
106
|
+
if (server.bridge.adapterId !== adapter.adapterId) {
|
|
107
|
+
throw new Error(`Local MCP bridge adapter ${adapter.adapterId} returned mismatched metadata`);
|
|
108
|
+
}
|
|
109
|
+
return server;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function boundedIdentity(value: string, name: string, max = 128): string {
|
|
113
|
+
if (value.length === 0 || value.length > max || /[\u0000-\u001f\u007f]/u.test(value)) {
|
|
114
|
+
throw new Error(`Local MCP bridge ${name} is invalid`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
package/src/openapi.ts
CHANGED
|
@@ -426,6 +426,22 @@ async function sendOpenApiRequest(
|
|
|
426
426
|
const headers = buildOperationHeaders(binding, args);
|
|
427
427
|
const body = buildOperationBody(binding, args, headers);
|
|
428
428
|
if (credential) applyCredentialPlacements(url, headers, credential);
|
|
429
|
+
if (credential?.authorizeProviderRequest) {
|
|
430
|
+
let authorized = false;
|
|
431
|
+
try {
|
|
432
|
+
authorized = await credential.authorizeProviderRequest();
|
|
433
|
+
} catch {
|
|
434
|
+
authorized = false;
|
|
435
|
+
}
|
|
436
|
+
if (!authorized) {
|
|
437
|
+
throw new IntegrationInvocationError(
|
|
438
|
+
"authorization_rejected",
|
|
439
|
+
"The connected account is no longer authorized for this operation",
|
|
440
|
+
"not_started",
|
|
441
|
+
false,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
429
445
|
return await fetchWithDeadline(
|
|
430
446
|
options.transport,
|
|
431
447
|
url,
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface IntegrationCredentialAudience {
|
|
|
37
37
|
export interface ResolvedIntegrationCredential {
|
|
38
38
|
readonly audience: IntegrationCredentialAudience;
|
|
39
39
|
readonly placements: readonly IntegrationCredentialPlacement[];
|
|
40
|
+
/** Exact accepted-attempt fence invoked immediately before one HTTP request. */
|
|
41
|
+
readonly authorizeProviderRequest?: () => Promise<boolean>;
|
|
40
42
|
readonly expiresAt?: string;
|
|
41
43
|
readonly scope?: readonly string[];
|
|
42
44
|
}
|