@ggui-ai/mcp-server 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +48 -0
  3. package/dist/admin-blueprints-transport.d.ts +114 -0
  4. package/dist/admin-blueprints-transport.d.ts.map +1 -0
  5. package/dist/admin-blueprints-transport.js +118 -0
  6. package/dist/admin-oauth-providers-transport.d.ts +40 -0
  7. package/dist/admin-oauth-providers-transport.d.ts.map +1 -0
  8. package/dist/admin-oauth-providers-transport.js +263 -0
  9. package/dist/auth.d.ts +39 -0
  10. package/dist/auth.d.ts.map +1 -0
  11. package/dist/auth.js +75 -0
  12. package/dist/build-mcp.d.ts +128 -0
  13. package/dist/build-mcp.d.ts.map +1 -0
  14. package/dist/build-mcp.js +113 -0
  15. package/dist/code-store-fs.d.ts +19 -0
  16. package/dist/code-store-fs.d.ts.map +1 -0
  17. package/dist/code-store-fs.js +98 -0
  18. package/dist/console-auth.d.ts +139 -0
  19. package/dist/console-auth.d.ts.map +1 -0
  20. package/dist/console-auth.js +102 -0
  21. package/dist/console-cache.d.ts +78 -0
  22. package/dist/console-cache.d.ts.map +1 -0
  23. package/dist/console-cache.js +105 -0
  24. package/dist/console-headers.d.ts +124 -0
  25. package/dist/console-headers.d.ts.map +1 -0
  26. package/dist/console-headers.js +49 -0
  27. package/dist/console-llm-trace.d.ts +66 -0
  28. package/dist/console-llm-trace.d.ts.map +1 -0
  29. package/dist/console-llm-trace.js +105 -0
  30. package/dist/console-payloads.d.ts +67 -0
  31. package/dist/console-payloads.d.ts.map +1 -0
  32. package/dist/console-payloads.js +105 -0
  33. package/dist/console-theme-routes.d.ts +111 -0
  34. package/dist/console-theme-routes.d.ts.map +1 -0
  35. package/dist/console-theme-routes.js +202 -0
  36. package/dist/console-timeline.d.ts +45 -0
  37. package/dist/console-timeline.d.ts.map +1 -0
  38. package/dist/console-timeline.js +169 -0
  39. package/dist/console-validator.d.ts +67 -0
  40. package/dist/console-validator.d.ts.map +1 -0
  41. package/dist/console-validator.js +105 -0
  42. package/dist/console-welcome.d.ts +7 -0
  43. package/dist/console-welcome.d.ts.map +1 -0
  44. package/dist/console-welcome.js +221 -0
  45. package/dist/csrf-middleware.d.ts +55 -0
  46. package/dist/csrf-middleware.d.ts.map +1 -0
  47. package/dist/csrf-middleware.js +138 -0
  48. package/dist/email-login.d.ts +174 -0
  49. package/dist/email-login.d.ts.map +1 -0
  50. package/dist/email-login.js +254 -0
  51. package/dist/email-resend.d.ts +29 -0
  52. package/dist/email-resend.d.ts.map +1 -0
  53. package/dist/email-resend.js +71 -0
  54. package/dist/email-sender-from-env.d.ts +34 -0
  55. package/dist/email-sender-from-env.d.ts.map +1 -0
  56. package/dist/email-sender-from-env.js +112 -0
  57. package/dist/email-smtp.d.ts +42 -0
  58. package/dist/email-smtp.d.ts.map +1 -0
  59. package/dist/email-smtp.js +81 -0
  60. package/dist/index.d.ts +102 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +122 -0
  63. package/dist/instructions-presets.d.ts +112 -0
  64. package/dist/instructions-presets.d.ts.map +1 -0
  65. package/dist/instructions-presets.js +195 -0
  66. package/dist/llm-backed-negotiator.d.ts +178 -0
  67. package/dist/llm-backed-negotiator.d.ts.map +1 -0
  68. package/dist/llm-backed-negotiator.js +579 -0
  69. package/dist/logger.d.ts +23 -0
  70. package/dist/logger.d.ts.map +1 -0
  71. package/dist/logger.js +41 -0
  72. package/dist/mcp-apps-inbound.d.ts +86 -0
  73. package/dist/mcp-apps-inbound.d.ts.map +1 -0
  74. package/dist/mcp-apps-inbound.js +278 -0
  75. package/dist/mcp-apps-outbound.d.ts +448 -0
  76. package/dist/mcp-apps-outbound.d.ts.map +1 -0
  77. package/dist/mcp-apps-outbound.js +1163 -0
  78. package/dist/mcp-mounts.d.ts +239 -0
  79. package/dist/mcp-mounts.d.ts.map +1 -0
  80. package/dist/mcp-mounts.js +222 -0
  81. package/dist/oauth-login-types.d.ts +160 -0
  82. package/dist/oauth-login-types.d.ts.map +1 -0
  83. package/dist/oauth-login-types.js +9 -0
  84. package/dist/oauth-login.d.ts +77 -0
  85. package/dist/oauth-login.d.ts.map +1 -0
  86. package/dist/oauth-login.js +455 -0
  87. package/dist/oauth-providers/github.d.ts +17 -0
  88. package/dist/oauth-providers/github.d.ts.map +1 -0
  89. package/dist/oauth-providers/github.js +89 -0
  90. package/dist/oauth-providers/google.d.ts +18 -0
  91. package/dist/oauth-providers/google.d.ts.map +1 -0
  92. package/dist/oauth-providers/google.js +59 -0
  93. package/dist/oauth-providers-store.d.ts +32 -0
  94. package/dist/oauth-providers-store.d.ts.map +1 -0
  95. package/dist/oauth-providers-store.js +291 -0
  96. package/dist/oauth.d.ts +347 -0
  97. package/dist/oauth.d.ts.map +1 -0
  98. package/dist/oauth.js +686 -0
  99. package/dist/pairing-transport.d.ts +99 -0
  100. package/dist/pairing-transport.d.ts.map +1 -0
  101. package/dist/pairing-transport.js +223 -0
  102. package/dist/rate-limit-middleware.d.ts +36 -0
  103. package/dist/rate-limit-middleware.d.ts.map +1 -0
  104. package/dist/rate-limit-middleware.js +57 -0
  105. package/dist/render-gate.d.ts +87 -0
  106. package/dist/render-gate.d.ts.map +1 -0
  107. package/dist/render-gate.js +77 -0
  108. package/dist/render-rate-limit.d.ts +59 -0
  109. package/dist/render-rate-limit.d.ts.map +1 -0
  110. package/dist/render-rate-limit.js +73 -0
  111. package/dist/render-signing.d.ts +98 -0
  112. package/dist/render-signing.d.ts.map +1 -0
  113. package/dist/render-signing.js +113 -0
  114. package/dist/request-context.d.ts +113 -0
  115. package/dist/request-context.d.ts.map +1 -0
  116. package/dist/request-context.js +154 -0
  117. package/dist/reserved-validators.d.ts +22 -0
  118. package/dist/reserved-validators.d.ts.map +1 -0
  119. package/dist/reserved-validators.js +101 -0
  120. package/dist/schema-compat.d.ts +167 -0
  121. package/dist/schema-compat.d.ts.map +1 -0
  122. package/dist/schema-compat.js +187 -0
  123. package/dist/security-headers-middleware.d.ts +38 -0
  124. package/dist/security-headers-middleware.d.ts.map +1 -0
  125. package/dist/security-headers-middleware.js +30 -0
  126. package/dist/server.d.ts +2060 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +6338 -0
  129. package/dist/session-channel.d.ts +651 -0
  130. package/dist/session-channel.d.ts.map +1 -0
  131. package/dist/session-channel.js +1756 -0
  132. package/dist/storage.d.ts +89 -0
  133. package/dist/storage.d.ts.map +1 -0
  134. package/dist/storage.js +171 -0
  135. package/dist/thread-transport.d.ts +118 -0
  136. package/dist/thread-transport.d.ts.map +1 -0
  137. package/dist/thread-transport.js +478 -0
  138. package/dist/user-session-auth.d.ts +167 -0
  139. package/dist/user-session-auth.d.ts.map +1 -0
  140. package/dist/user-session-auth.js +148 -0
  141. package/package.json +76 -0
@@ -0,0 +1,263 @@
1
+ import { resolveIdentity, UnauthenticatedError } from './auth.js';
2
+ export const DEFAULT_ADMIN_OAUTH_PROVIDERS_PATH = '/ggui/admin/oauth-providers';
3
+ function redact(record) {
4
+ return {
5
+ providerId: record.providerId,
6
+ clientId: record.clientId,
7
+ clientSecret: '<redacted>',
8
+ source: record.source,
9
+ enabled: record.enabled,
10
+ };
11
+ }
12
+ function isEnvOverriddenError(err) {
13
+ return err instanceof Error && err.message.startsWith('oauth_provider_env_overridden');
14
+ }
15
+ function isInvalidIdError(err) {
16
+ return (err instanceof Error &&
17
+ (err.message.startsWith('oauth_provider_invalid_id') ||
18
+ err.message.startsWith('oauth_provider_invalid_client_id') ||
19
+ err.message.startsWith('oauth_provider_invalid_client_secret')));
20
+ }
21
+ function isNotFoundError(err) {
22
+ return err instanceof Error && err.message.startsWith('oauth_provider_not_found');
23
+ }
24
+ export function mountAdminOAuthProvidersTransport(app, opts) {
25
+ const basePath = opts.path ?? DEFAULT_ADMIN_OAUTH_PROVIDERS_PATH;
26
+ const auditSink = opts.auditSink;
27
+ const emitAudit = async (entry, auditLogger) => {
28
+ if (!auditSink)
29
+ return;
30
+ try {
31
+ await auditSink.record({ at: Date.now(), ...entry });
32
+ }
33
+ catch (err) {
34
+ auditLogger.warn('audit_emit_failed', {
35
+ action: entry.action,
36
+ error: String(err),
37
+ });
38
+ }
39
+ };
40
+ // Builder-identity gate. Same shape as /admin/blueprints — 401 on
41
+ // missing/invalid bearer, 403 on non-builder. Returns true when
42
+ // the route handler may proceed; false when a response was already
43
+ // written.
44
+ const requireBuilder = async (req, res, reqLogger) => {
45
+ try {
46
+ const identity = await resolveIdentity(opts.auth, req);
47
+ if (identity.identity.kind !== 'builder') {
48
+ reqLogger.warn('admin_oauth_providers_forbidden', {
49
+ identityKind: identity.identity.kind,
50
+ });
51
+ res.status(403).json({
52
+ error: {
53
+ code: 'forbidden',
54
+ message: 'Admin OAuth providers requires a builder identity.',
55
+ },
56
+ });
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ catch (err) {
62
+ if (err instanceof UnauthenticatedError) {
63
+ reqLogger.warn('admin_oauth_providers_unauthenticated', {
64
+ reason: err.message,
65
+ });
66
+ res.status(401).json({
67
+ error: { code: 'unauthenticated', message: err.message },
68
+ });
69
+ return false;
70
+ }
71
+ reqLogger.error('admin_oauth_providers_unexpected_error', {
72
+ error: String(err),
73
+ });
74
+ res.status(500).json({
75
+ error: { code: 'internal', message: 'Internal server error' },
76
+ });
77
+ return false;
78
+ }
79
+ };
80
+ // --- GET base ---
81
+ app.get(basePath, async (req, res) => {
82
+ const reqLogger = opts.logger.child({ route: 'GET ' + basePath });
83
+ if (!(await requireBuilder(req, res, reqLogger)))
84
+ return;
85
+ try {
86
+ const records = await opts.store.list();
87
+ res.status(200).json({ providers: records.map(redact) });
88
+ }
89
+ catch (err) {
90
+ reqLogger.error('admin_oauth_providers_list_failed', {
91
+ error: String(err),
92
+ });
93
+ res.status(500).json({
94
+ error: { code: 'internal', message: 'Internal server error' },
95
+ });
96
+ }
97
+ });
98
+ // --- PUT base/:providerId ---
99
+ app.put(`${basePath}/:providerId`, async (req, res) => {
100
+ const reqLogger = opts.logger.child({ route: 'PUT ' + basePath + '/:providerId' });
101
+ if (!(await requireBuilder(req, res, reqLogger)))
102
+ return;
103
+ const providerId = req.params['providerId'] ?? '';
104
+ const body = (req.body ?? {});
105
+ const clientId = typeof body['clientId'] === 'string' ? body['clientId'] : undefined;
106
+ const clientSecret = typeof body['clientSecret'] === 'string' ? body['clientSecret'] : undefined;
107
+ const enabled = typeof body['enabled'] === 'boolean' ? body['enabled'] : undefined;
108
+ if (!clientId || !clientSecret) {
109
+ reqLogger.debug?.('admin_oauth_providers_bad_request', {
110
+ hasClientId: clientId !== undefined,
111
+ hasClientSecret: clientSecret !== undefined,
112
+ });
113
+ res.status(400).json({
114
+ error: {
115
+ code: 'bad_request',
116
+ message: 'Body requires non-empty string `clientId` and `clientSecret`.',
117
+ },
118
+ });
119
+ return;
120
+ }
121
+ try {
122
+ const record = await opts.store.put({
123
+ providerId,
124
+ clientId,
125
+ clientSecret,
126
+ ...(enabled !== undefined ? { enabled } : {}),
127
+ });
128
+ reqLogger.info('admin_oauth_providers_put', { providerId });
129
+ await emitAudit({
130
+ action: 'auth.oauth-config.write',
131
+ actor: { kind: 'builder' },
132
+ resource: { kind: 'oauth-provider', id: providerId },
133
+ metadata: { enabled: record.enabled },
134
+ }, reqLogger);
135
+ res.status(200).json(redact(record));
136
+ }
137
+ catch (err) {
138
+ if (isEnvOverriddenError(err)) {
139
+ reqLogger.warn('admin_oauth_providers_env_overridden', { providerId });
140
+ res.status(409).json({
141
+ error: {
142
+ code: 'env_overridden',
143
+ message: err.message,
144
+ },
145
+ });
146
+ return;
147
+ }
148
+ if (isInvalidIdError(err)) {
149
+ reqLogger.warn('admin_oauth_providers_validation_failed', {
150
+ providerId,
151
+ reason: err.message,
152
+ });
153
+ res.status(400).json({
154
+ error: { code: 'bad_request', message: err.message },
155
+ });
156
+ return;
157
+ }
158
+ reqLogger.error('admin_oauth_providers_put_failed', {
159
+ providerId,
160
+ error: String(err),
161
+ });
162
+ res.status(500).json({
163
+ error: { code: 'internal', message: 'Internal server error' },
164
+ });
165
+ }
166
+ });
167
+ // --- POST base/:providerId/toggle ---
168
+ app.post(`${basePath}/:providerId/toggle`, async (req, res) => {
169
+ const reqLogger = opts.logger.child({
170
+ route: 'POST ' + basePath + '/:providerId/toggle',
171
+ });
172
+ if (!(await requireBuilder(req, res, reqLogger)))
173
+ return;
174
+ const providerId = req.params['providerId'] ?? '';
175
+ const body = (req.body ?? {});
176
+ const enabled = typeof body['enabled'] === 'boolean' ? body['enabled'] : undefined;
177
+ if (enabled === undefined) {
178
+ res.status(400).json({
179
+ error: {
180
+ code: 'bad_request',
181
+ message: 'Body requires boolean `enabled`.',
182
+ },
183
+ });
184
+ return;
185
+ }
186
+ try {
187
+ await opts.store.setEnabled(providerId, enabled);
188
+ reqLogger.info('admin_oauth_providers_toggle', { providerId, enabled });
189
+ await emitAudit({
190
+ action: 'auth.oauth-config.write',
191
+ actor: { kind: 'builder' },
192
+ resource: { kind: 'oauth-provider', id: providerId },
193
+ metadata: { enabled },
194
+ }, reqLogger);
195
+ res.status(204).end();
196
+ }
197
+ catch (err) {
198
+ if (isEnvOverriddenError(err)) {
199
+ reqLogger.warn('admin_oauth_providers_env_overridden', { providerId });
200
+ res.status(409).json({
201
+ error: {
202
+ code: 'env_overridden',
203
+ message: err.message,
204
+ },
205
+ });
206
+ return;
207
+ }
208
+ if (isNotFoundError(err)) {
209
+ res.status(404).json({
210
+ error: { code: 'not_found', message: err.message },
211
+ });
212
+ return;
213
+ }
214
+ if (isInvalidIdError(err)) {
215
+ res.status(400).json({
216
+ error: { code: 'bad_request', message: err.message },
217
+ });
218
+ return;
219
+ }
220
+ reqLogger.error('admin_oauth_providers_toggle_failed', {
221
+ providerId,
222
+ error: String(err),
223
+ });
224
+ res.status(500).json({
225
+ error: { code: 'internal', message: 'Internal server error' },
226
+ });
227
+ }
228
+ });
229
+ // --- DELETE base/:providerId ---
230
+ app.delete(`${basePath}/:providerId`, async (req, res) => {
231
+ const reqLogger = opts.logger.child({
232
+ route: 'DELETE ' + basePath + '/:providerId',
233
+ });
234
+ if (!(await requireBuilder(req, res, reqLogger)))
235
+ return;
236
+ const providerId = req.params['providerId'] ?? '';
237
+ try {
238
+ await opts.store.remove(providerId);
239
+ reqLogger.info('admin_oauth_providers_remove', { providerId });
240
+ await emitAudit({
241
+ action: 'auth.oauth-config.delete',
242
+ actor: { kind: 'builder' },
243
+ resource: { kind: 'oauth-provider', id: providerId },
244
+ }, reqLogger);
245
+ res.status(204).end();
246
+ }
247
+ catch (err) {
248
+ if (isInvalidIdError(err)) {
249
+ res.status(400).json({
250
+ error: { code: 'bad_request', message: err.message },
251
+ });
252
+ return;
253
+ }
254
+ reqLogger.error('admin_oauth_providers_remove_failed', {
255
+ providerId,
256
+ error: String(err),
257
+ });
258
+ res.status(500).json({
259
+ error: { code: 'internal', message: 'Internal server error' },
260
+ });
261
+ }
262
+ });
263
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * HTTP → AuthAdapter bridge.
3
+ *
4
+ * Parses `Authorization: Bearer <token>` off the incoming request and
5
+ * delegates identity resolution to the supplied {@link AuthAdapter}.
6
+ * Unauthenticated requests are rejected with 401 before the MCP SDK
7
+ * ever sees the request body.
8
+ */
9
+ import type { Request } from 'express';
10
+ import type { IncomingHttpHeaders } from 'node:http';
11
+ import type { AuthAdapter, AuthResult } from '@ggui-ai/mcp-server-core';
12
+ export declare class UnauthenticatedError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * Resolve an identity from a normalized `(headers, remoteAddress)` pair.
17
+ * Both the Express `/mcp` path and the WebSocket live-channel `/ws`
18
+ * upgrade path funnel through this — so a single bearer-parsing +
19
+ * adapter-call codepath covers every OSS ingress point.
20
+ */
21
+ export declare function resolveIdentityFromHeaders(adapter: AuthAdapter, headers: IncomingHttpHeaders, remoteAddress?: string): Promise<AuthResult>;
22
+ /**
23
+ * Resolve the caller's identity from the incoming Express request.
24
+ * Throws {@link UnauthenticatedError} when the adapter returns null
25
+ * so the HTTP layer can map to a single 401 response shape.
26
+ */
27
+ export declare function resolveIdentity(adapter: AuthAdapter, req: Request): Promise<AuthResult>;
28
+ /**
29
+ * Derive a stable `appId` from an auth result.
30
+ *
31
+ * In OSS single-user mode every identity collapses to `{kind:'builder'}`
32
+ * which doesn't carry a tenant id. We fold these into a single
33
+ * well-known value (`DEFAULT_BUILDER_APP_ID`) so blueprint / vector
34
+ * scoping still works. Multi-tenant bindings (a hosted closed runtime)
35
+ * override this by passing `appIdFromIdentity` on `createGguiServer`.
36
+ */
37
+ export declare const DEFAULT_BUILDER_APP_ID = "builder";
38
+ export declare function defaultAppIdFromIdentity(result: AuthResult): string;
39
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAExE,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,mBAAmB,EAC5B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,UAAU,CAAC,CAMrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAqBnE"}
package/dist/auth.js ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * HTTP → AuthAdapter bridge.
3
+ *
4
+ * Parses `Authorization: Bearer <token>` off the incoming request and
5
+ * delegates identity resolution to the supplied {@link AuthAdapter}.
6
+ * Unauthenticated requests are rejected with 401 before the MCP SDK
7
+ * ever sees the request body.
8
+ */
9
+ export class UnauthenticatedError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'UnauthenticatedError';
13
+ }
14
+ }
15
+ /**
16
+ * Resolve an identity from a normalized `(headers, remoteAddress)` pair.
17
+ * Both the Express `/mcp` path and the WebSocket live-channel `/ws`
18
+ * upgrade path funnel through this — so a single bearer-parsing +
19
+ * adapter-call codepath covers every OSS ingress point.
20
+ */
21
+ export async function resolveIdentityFromHeaders(adapter, headers, remoteAddress) {
22
+ const flat = {};
23
+ for (const [k, v] of Object.entries(headers)) {
24
+ if (typeof v === 'string')
25
+ flat[k] = v;
26
+ else if (Array.isArray(v))
27
+ flat[k] = v[0];
28
+ }
29
+ const result = await adapter.getIdentity({
30
+ headers: flat,
31
+ remoteAddress,
32
+ });
33
+ if (!result) {
34
+ throw new UnauthenticatedError('No valid credentials');
35
+ }
36
+ return result;
37
+ }
38
+ /**
39
+ * Resolve the caller's identity from the incoming Express request.
40
+ * Throws {@link UnauthenticatedError} when the adapter returns null
41
+ * so the HTTP layer can map to a single 401 response shape.
42
+ */
43
+ export async function resolveIdentity(adapter, req) {
44
+ return resolveIdentityFromHeaders(adapter, req.headers, req.socket?.remoteAddress ?? req.ip ?? undefined);
45
+ }
46
+ /**
47
+ * Derive a stable `appId` from an auth result.
48
+ *
49
+ * In OSS single-user mode every identity collapses to `{kind:'builder'}`
50
+ * which doesn't carry a tenant id. We fold these into a single
51
+ * well-known value (`DEFAULT_BUILDER_APP_ID`) so blueprint / vector
52
+ * scoping still works. Multi-tenant bindings (a hosted closed runtime)
53
+ * override this by passing `appIdFromIdentity` on `createGguiServer`.
54
+ */
55
+ export const DEFAULT_BUILDER_APP_ID = 'builder';
56
+ export function defaultAppIdFromIdentity(result) {
57
+ if (result.identity.kind === 'user') {
58
+ // Cloud auth adapters populate `appId` when known — per-app-scoped
59
+ // bearer key, URL-path-derived, or User.defaultAppId lookup. Read
60
+ // it with priority so handlers scope to the correct GguiApp.appId.
61
+ // OSS deployments leave the field undefined and fall through to the
62
+ // `workspaceId`/`userId` chain.
63
+ return (result.identity.appId ??
64
+ result.identity.workspaceId ??
65
+ result.identity.userId);
66
+ }
67
+ // `kind: 'app'` carries the appId directly — surfaced by API-key /
68
+ // OAuth-bearer adapters (e.g. an ApiKeyAuthAdapter on a hosted
69
+ // multi-tenant deployment). Falling through to DEFAULT_BUILDER_APP_ID
70
+ // here would discard the very tenant id the adapter just proved.
71
+ if (result.identity.kind === 'app') {
72
+ return result.identity.appId;
73
+ }
74
+ return DEFAULT_BUILDER_APP_ID;
75
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * buildMcpServer — register every shared handler on a fresh `McpServer`
3
+ * instance. One server per request (matches the hosted pattern); the
4
+ * `StreamableHTTPServerTransport` holds per-connection state so pooling
5
+ * isn't worth the locking.
6
+ *
7
+ * Output validation runs here via a zod object built from each handler's
8
+ * `outputSchema` raw shape. This enforces the ggui convention that every
9
+ * tool return advertises its shape — wire consumers can trust the output.
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { type ZodRawShape } from 'zod';
13
+ import type { HandlerContext, SharedHandler } from '@ggui-ai/mcp-server-handlers';
14
+ import type { Logger } from './logger.js';
15
+ import { type GguiSessionResourceTemplateOptions } from './mcp-apps-outbound.js';
16
+ export interface ServerInfo {
17
+ readonly name: string;
18
+ readonly version: string;
19
+ readonly description?: string;
20
+ }
21
+ export interface BuildMcpServerOptions {
22
+ /**
23
+ * When set, register the MCP Apps outbound wiring on every fresh
24
+ * server instance — advertises the `io.modelcontextprotocol/ui`
25
+ * capability and serves `ui://ggui/session` via `resources/read`.
26
+ *
27
+ * Tool-declaration `_meta.ui.*` is INDEPENDENT of this flag; it's
28
+ * carried per-handler on `SharedHandler._meta`. A server can stamp
29
+ * those without turning the outbound wiring on, but serving the
30
+ * resource without stamping the declaration is pointless, so the
31
+ * canonical path is "enable both together" via `createGguiServer`.
32
+ */
33
+ readonly mcpAppsOutbound?: boolean;
34
+ /**
35
+ * Optional override for the `ui://ggui/session` shell body. Defaults
36
+ * to whatever shell the server was built with — either a placeholder
37
+ * or the real thin-shell HTML.
38
+ */
39
+ readonly shellHtml?: string;
40
+ /**
41
+ * Per-session self-contained shell options. When supplied,
42
+ * `installMcpAppsOutbound` ALSO registers
43
+ * `ui://ggui/session/{sessionId}` as a resource template — the URI
44
+ * `ggui_push.resultMeta` stamps on per-call `_meta.ui.resourceUri`
45
+ * for third-party MCP Apps hosts (Claude Desktop, claude.ai web)
46
+ * that don't speak ggui's custom postMessage protocol.
47
+ *
48
+ * Absent → only the legacy postMessage shell is registered (first-
49
+ * party hosts only).
50
+ */
51
+ readonly selfContained?: GguiSessionResourceTemplateOptions;
52
+ /**
53
+ * Public origin the server is reachable at — forwarded to
54
+ * `installMcpAppsOutbound` so the static `ui://ggui/session`
55
+ * resource declares `_meta.ui.csp.{connectDomains,resourceDomains}`.
56
+ * Without this, spec-compliant hosts (Claude Desktop, claude.ai
57
+ * Connector, Claude Code) apply their default CSP (`connect-src
58
+ * 'none'`) and the iframe can't fetch the runtime bundle or open
59
+ * the WebSocket. Omit when running same-origin behind a first-party
60
+ * host that owns the iframe CSP itself.
61
+ */
62
+ readonly publicBaseUrl?: string;
63
+ /**
64
+ * Identity-kind allowlist for tool registration. When set, handlers
65
+ * whose `allowedFor` field is non-empty AND does NOT intersect this
66
+ * list are skipped at registration time (NOT registered with the MCP
67
+ * server, NOT visible in `tools/list`).
68
+ *
69
+ * Handlers without `allowedFor` are registered unconditionally per the
70
+ * "anyone authenticated" default in
71
+ * `packages/mcp-server-handlers/src/types.ts:151-153`. Omitting this
72
+ * option (or passing `undefined`) disables filtering entirely —
73
+ * today's behavior, kept for OSS callers (resolved as
74
+ * `kind: 'builder'`) so an OSS deployment never accidentally gates
75
+ * itself off.
76
+ *
77
+ * Production postures:
78
+ * - agent-builder posture: `allowedKinds: ['app']`
79
+ * - end-user / Connector posture: `allowedKinds: ['user']`
80
+ * - OSS local: omit (every handler registers regardless)
81
+ */
82
+ readonly allowedKinds?: ReadonlyArray<'app' | 'user' | 'builder'>;
83
+ /**
84
+ * Server-level instructions string injected into the MCP
85
+ * `InitializeResult.instructions` field. Hosts (Claude.ai web,
86
+ * Claude Desktop, MCP Inspector) inject this into the LLM's system
87
+ * prompt as a top-level block, ABOVE per-tool descriptions —
88
+ * influencing "how should I behave with this server's tools
89
+ * generally?" vs. per-tool "should I pick THIS tool right now?"
90
+ *
91
+ * Resolved upstream by `resolveMcpInstructions` from a preset name
92
+ * or arbitrary string. Pass `undefined` here to omit the field
93
+ * (host falls back to per-tool descriptions only).
94
+ *
95
+ * See `instructions-presets.ts` for the supported preset enum and
96
+ * full rationale.
97
+ */
98
+ readonly instructions?: string;
99
+ /**
100
+ * Hooks invoked once the per-request `McpServer` is constructed,
101
+ * after the MCP-Apps outbound install (when enabled) and before
102
+ * any tool registration. Each entry receives the fresh `McpServer`
103
+ * and may register additional resources / resource templates.
104
+ *
105
+ * Use case: hosted deployments that mount cross-cutting MCP App
106
+ * UI bundles (e.g. a `ui://`-scheme resource for welcome /
107
+ * account-status cards) without baking the bundle's wiring into
108
+ * this OSS factory. The closure runs on every fresh server
109
+ * instance, mirroring the per-request `installMcpAppsOutbound`
110
+ * lifecycle.
111
+ *
112
+ * Each registrar SHOULD be idempotent across calls (the underlying
113
+ * SDK throws on duplicate URIs anyway). Errors thrown by a
114
+ * registrar propagate up — the request fails before any tool can
115
+ * dispatch, surfacing misconfiguration loudly rather than 404-ing
116
+ * `resources/read` later.
117
+ */
118
+ readonly extraResources?: ReadonlyArray<(server: McpServer) => void>;
119
+ }
120
+ /**
121
+ * Build a fresh MCP server with every handler registered.
122
+ *
123
+ * `getContext` is a late-binding accessor so the HTTP layer can thread
124
+ * per-request context (via AsyncLocalStorage or a closure) without
125
+ * leaking the shape into this module.
126
+ */
127
+ export declare function buildMcpServer(info: ServerInfo, handlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>, getContext: () => HandlerContext, logger: Logger, opts?: BuildMcpServerOptions): McpServer;
128
+ //# sourceMappingURL=build-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-mcp.d.ts","sourceRoot":"","sources":["../src/build-mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAEL,KAAK,kCAAkC,EACxC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,kCAAkC,CAAC;IAC5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAElE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC;CACtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EAChE,UAAU,EAAE,MAAM,cAAc,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,qBAA0B,GAC/B,SAAS,CA6FX"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * buildMcpServer — register every shared handler on a fresh `McpServer`
3
+ * instance. One server per request (matches the hosted pattern); the
4
+ * `StreamableHTTPServerTransport` holds per-connection state so pooling
5
+ * isn't worth the locking.
6
+ *
7
+ * Output validation runs here via a zod object built from each handler's
8
+ * `outputSchema` raw shape. This enforces the ggui convention that every
9
+ * tool return advertises its shape — wire consumers can trust the output.
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { z } from 'zod';
13
+ import { installMcpAppsOutbound, } from './mcp-apps-outbound.js';
14
+ /**
15
+ * Build a fresh MCP server with every handler registered.
16
+ *
17
+ * `getContext` is a late-binding accessor so the HTTP layer can thread
18
+ * per-request context (via AsyncLocalStorage or a closure) without
19
+ * leaking the shape into this module.
20
+ */
21
+ export function buildMcpServer(info, handlers, getContext, logger, opts = {}) {
22
+ const server = new McpServer({
23
+ name: info.name,
24
+ version: info.version,
25
+ ...(info.description ? { description: info.description } : {}),
26
+ ...(opts.instructions ? { instructions: opts.instructions } : {}),
27
+ });
28
+ if (opts.mcpAppsOutbound) {
29
+ installMcpAppsOutbound(server, {
30
+ ...(opts.shellHtml !== undefined ? { shellHtml: opts.shellHtml } : {}),
31
+ ...(opts.selfContained !== undefined
32
+ ? { selfContained: opts.selfContained }
33
+ : {}),
34
+ ...(opts.publicBaseUrl !== undefined
35
+ ? { publicBaseUrl: opts.publicBaseUrl }
36
+ : {}),
37
+ });
38
+ }
39
+ // Per-request resource registrars supplied by the host. Run BEFORE
40
+ // tool registration so `tools/list` ordering is unaffected and any
41
+ // registrar-thrown error fails the request before tool dispatch.
42
+ if (opts.extraResources) {
43
+ for (const register of opts.extraResources) {
44
+ register(server);
45
+ }
46
+ }
47
+ const allowedKinds = opts.allowedKinds;
48
+ for (const handler of handlers) {
49
+ // Identity-kind gate. Skipping at registration time (rather than at
50
+ // call dispatch) means a curated deployment's `tools/list` reflects
51
+ // exactly what callers can use — no "ghost" tools that 401 on
52
+ // invocation. Handlers without `allowedFor` register regardless.
53
+ if (allowedKinds !== undefined
54
+ && handler.allowedFor !== undefined
55
+ && handler.allowedFor.length > 0
56
+ && !handler.allowedFor.some((kind) => allowedKinds.includes(kind))) {
57
+ continue;
58
+ }
59
+ server.registerTool(handler.name, {
60
+ ...(handler.title ? { title: handler.title } : {}),
61
+ description: handler.description,
62
+ inputSchema: handler.inputSchema,
63
+ outputSchema: handler.outputSchema,
64
+ // Forward declaration-level `_meta` (e.g. `_meta.ui.resourceUri`
65
+ // / `_meta.ui.visibility` stamped by the MCP Apps outbound path).
66
+ // Opaque to the transport — hosts consume it per their own spec.
67
+ ...(handler._meta ? { _meta: handler._meta } : {}),
68
+ }, async (input) => {
69
+ const ctx = getContext();
70
+ const start = Date.now();
71
+ try {
72
+ const data = await handler.handler(input, ctx);
73
+ const validated = z.object(handler.outputSchema).parse(data);
74
+ // Per-result `_meta` — NOT merged into structuredContent, so
75
+ // agents that typecheck against the tool signature never see
76
+ // it. This is where view-only bootstrap material lives.
77
+ const meta = await handler.resultMeta?.(data, input, ctx);
78
+ logger.info('tool_invoked', {
79
+ tool: handler.name,
80
+ appId: ctx.appId,
81
+ outcome: 'success',
82
+ elapsedMs: Date.now() - start,
83
+ });
84
+ return {
85
+ structuredContent: validated,
86
+ content: [
87
+ { type: 'text', text: JSON.stringify(validated) },
88
+ ],
89
+ ...(meta !== undefined ? { _meta: meta } : {}),
90
+ };
91
+ }
92
+ catch (err) {
93
+ logger.warn('tool_invoked', {
94
+ tool: handler.name,
95
+ appId: ctx.appId,
96
+ outcome: 'error',
97
+ errorClass: errorClassName(err),
98
+ elapsedMs: Date.now() - start,
99
+ });
100
+ throw err;
101
+ }
102
+ });
103
+ }
104
+ return server;
105
+ }
106
+ function errorClassName(err) {
107
+ if (err instanceof Error) {
108
+ if (err.name && err.name !== 'Error')
109
+ return err.name;
110
+ return err.constructor.name || 'Error';
111
+ }
112
+ return 'Unknown';
113
+ }
@@ -0,0 +1,19 @@
1
+ import { type CodeStore } from '@ggui-ai/mcp-server-core';
2
+ /** Options for {@link FileSystemCodeStore}. */
3
+ export interface FileSystemCodeStoreOptions {
4
+ /**
5
+ * Filesystem root for the cache. Defaults to `~/.ggui/code-cache/`.
6
+ * Tests typically override to a project-local tmp dir.
7
+ */
8
+ readonly root?: string;
9
+ }
10
+ export declare class FileSystemCodeStore implements CodeStore {
11
+ private readonly root;
12
+ constructor(opts?: FileSystemCodeStoreOptions);
13
+ /** Absolute on-disk path for a given hash. Two-level sharded layout. */
14
+ private absPath;
15
+ put(hash: string, code: string): Promise<void>;
16
+ get(hash: string): Promise<string | null>;
17
+ hashOf(code: string): string;
18
+ }
19
+ //# sourceMappingURL=code-store-fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-store-fs.d.ts","sourceRoot":"","sources":["../src/code-store-fs.ts"],"names":[],"mappings":"AAmDA,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,0BAA0B,CAAC;AAElC,+CAA+C;AAC/C,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAoB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,IAAI,GAAE,0BAA+B;IAIjD,wEAAwE;IACxE,OAAO,CAAC,OAAO;IAMT,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB9C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAU/C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAG7B"}