@theoremjs/react 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/@theoremjs/react?logo=npm&label=npm&color=cb3837)](https://www.npmjs.com/package/@theoremjs/react)
4
4
  [![CI](https://github.com/masudl-hub/theoremai/actions/workflows/ci.yml/badge.svg)](https://github.com/masudl-hub/theoremai/actions/workflows/ci.yml)
5
5
  [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/masudl-hub/theoremai/blob/main/LICENSE)
6
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/masudl-hub/theoremai)
6
7
 
7
8
  Define an agent once. Get the server and the user interface from the same definition.
8
9
 
@@ -250,6 +251,23 @@ The handler accepts these requests:
250
251
 
251
252
  The console builds a form from the input schema. It shows the result as figures, charts, tables, images, audio, or Markdown, with the raw JSON beside it. If the data renderer fails, the view switches to JSON so the selected view matches what is shown. For your own UI, use `createHostTransport` and `useTheoremHost`.
252
253
 
254
+ #### Serve a host profile over MCP
255
+
256
+ `createTheoremMcpHandler` serves the same profile to MCP clients, such as coding agents and chat apps, at one endpoint. It answers MCP clients of the current revision and clients that still open with `initialize`, and it keeps no session between requests.
257
+
258
+ ```ts
259
+ import { createTheoremMcpHandler } from '@theoremjs/react/server';
260
+
261
+ export const mcp = createTheoremMcpHandler({ profile: docsTools, version: '1.0.0' }); // mount at /mcp
262
+ ```
263
+
264
+ - `tools/list` gives each allowed tool with its input schema. A tool's `access` becomes the read-only and destructive hints of MCP.
265
+ - Clients of the current revision may cache the tool list for 5 minutes and the discovery result for an hour. Neither holds anything about the caller, so a shared cache may keep them.
266
+ - `tools/call` runs the tool through the gates, the guardrails, and the trace of the kernel. The client receives the text a model would read, not the raw output.
267
+ - A tool that fails returns a result marked `isError`, so the model can read it and try again.
268
+ - No one can approve a call over MCP. The handler refuses a profile when an allowed tool has a permission other than `auto`.
269
+ - A browser page on another origin is refused unless `origins` lists it.
270
+
253
271
  ### Decision
254
272
 
255
273
  A `decision` profile has no conversation. You give it a JSON state and questions. It answers the questions about the state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/react",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -60,7 +60,7 @@
60
60
  "@stylexjs/stylex": "0.19.0",
61
61
  "react": "^19.0.0",
62
62
  "react-dom": "^19.0.0",
63
- "@theoremjs/agents": "^0.3.1"
63
+ "@theoremjs/agents": "^0.3.3"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "@astryxdesign/core": {
@@ -150,3 +150,9 @@ export declare function createTheoremHandler(options: TheoremHandlerOptions): (r
150
150
  * directly, and every call runs through the kernel's gates and guardrails.
151
151
  */
152
152
  export declare function createTheoremHostHandler(options: TheoremHostHandlerOptions): (request: Request) => Promise<Response>;
153
+ /** The host profile a handler serves; any other type is the builder's mistake, refused at setup. */
154
+ export declare function defineHostProfile(handler: string, definition: TheoremHostHandlerOptions['profile']): Extract<Profile, {
155
+ type: 'host';
156
+ }>;
157
+ /** A registered tool a host profile can serve: builtins run only inside a model's turn. */
158
+ export declare function hostTool(name: string): import("../../../src/kernel/tools/types.ts").RegisteredAgentTool | import("../../../src/kernel/tools/types.ts").FunctionToolDef<unknown, unknown> | import("../../../src/kernel/tools/types.ts").HttpToolDef<unknown, unknown> | import("../../../src/kernel/tools/types.ts").McpToolDef<unknown, unknown> | undefined;
package/server/handler.js CHANGED
@@ -652,13 +652,8 @@ async function settlingOf(ctx, request, session, body) {
652
652
  }
653
653
  async function hostRoute(ctx, request, session) {
654
654
  const target = routeOf(request);
655
- if (request.method === 'GET' && target === '') {
656
- const tool = (name) => {
657
- const registered = defaultKernelScope.tools.get(name);
658
- return registered?.type === 'builtin' ? undefined : registered;
659
- };
660
- return jsonResponse(200, { interface: hostInterface(ctx.profile, tool) });
661
- }
655
+ if (request.method === 'GET' && target === '')
656
+ return jsonResponse(200, { interface: hostInterface(ctx.profile, hostTool) });
662
657
  if (request.method === 'POST' && target === 'call') {
663
658
  const body = await readBody(request, theoremHostCallRequestSchema, ctx.options.maxRequestBytes);
664
659
  return eventStream(ctx, request, () => callEvents(ctx, request, session, body));
@@ -731,12 +726,21 @@ export function createTheoremHandler(options) {
731
726
  * directly, and every call runs through the kernel's gates and guardrails.
732
727
  */
733
728
  export function createTheoremHostHandler(options) {
734
- const profile = defineProfile(options.profile);
729
+ return serve('createTheoremHostHandler', defineHostProfile('createTheoremHostHandler', options.profile), options);
730
+ }
731
+ /** The host profile a handler serves; any other type is the builder's mistake, refused at setup. */
732
+ export function defineHostProfile(handler, definition) {
733
+ const profile = defineProfile(definition);
735
734
  if (profile.type !== 'host') {
736
735
  // labels-exempt: builder config error at setup; no user sees it
737
- throw new Error(`createTheoremHostHandler serves type 'host'; got type '${profile.type}'.`);
736
+ throw new Error(`${handler} serves type 'host'; got type '${profile.type}'.`);
738
737
  }
739
- return serve('createTheoremHostHandler', profile, options);
738
+ return profile;
739
+ }
740
+ /** A registered tool a host profile can serve: builtins run only inside a model's turn. */
741
+ export function hostTool(name) {
742
+ const registered = defaultKernelScope.tools.get(name);
743
+ return registered?.type === 'builtin' ? undefined : registered;
740
744
  }
741
745
  function serve(name, profile, options, chatOptions = {}) {
742
746
  const gateTtlMs = resolveGateTtlMs(name, options.gateTtlMs);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * One Web-standard handler that serves a host profile's tools to MCP clients over
3
+ * Streamable HTTP, at one endpoint that takes POST.
4
+ *
5
+ * - Clients on the current revision (`2026-07-28`) send each request with its version and
6
+ * capabilities in `_meta`; nothing is kept between requests.
7
+ * - Clients on an earlier revision open with `initialize`; the handler answers it and serves
8
+ * them the same way, without a session.
9
+ *
10
+ * Every call runs through the kernel as a host call does: the profile's gates, guardrails and
11
+ * trace. The client reads what a model would: the call's guarded text, never its raw output.
12
+ *
13
+ * @module
14
+ */
15
+ import { type ClientTurnOptions } from '@theoremjs/agents/host';
16
+ import { type TheoremHostHandlerOptions } from './handler.ts';
17
+ export type TheoremMcpHandlerOptions = {
18
+ /** Host profile to serve. Registered with the kernel when the handler is created. */
19
+ profile: TheoremHostHandlerOptions['profile'];
20
+ /** The server's version, as clients show it. */
21
+ version: string;
22
+ /** The server's name, as clients show it. Defaults to the profile's id. */
23
+ name?: string;
24
+ /** How a model should use these tools; clients may add it to the model's context. */
25
+ instructions?: string;
26
+ /**
27
+ * Origins a browser page may call from, besides the endpoint's own. A request whose
28
+ * `Origin` is neither is refused with 403, as MCP requires against DNS rebinding.
29
+ */
30
+ origins?: readonly string[];
31
+ /** Handed to each tool as `ctx.host`. */
32
+ host?: (request: Request) => unknown;
33
+ /** Largest request body read, in bytes. */
34
+ maxRequestBytes?: number;
35
+ /** Who reads this host's replies: forwarded to `clientError` for a failure the handler catches. */
36
+ clientEvents?: ClientTurnOptions;
37
+ /** Called with any error the handler catches, for reporting. */
38
+ onError?: (err: unknown, ctx: {
39
+ request: Request;
40
+ }) => void;
41
+ };
42
+ /**
43
+ * Serves a host profile's tools to MCP clients. Every tool the profile allows must run
44
+ * without a person's approval: an MCP client has no one to ask.
45
+ */
46
+ export declare function createTheoremMcpHandler(options: TheoremMcpHandlerOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,312 @@
1
+ /**
2
+ * One Web-standard handler that serves a host profile's tools to MCP clients over
3
+ * Streamable HTTP, at one endpoint that takes POST.
4
+ *
5
+ * - Clients on the current revision (`2026-07-28`) send each request with its version and
6
+ * capabilities in `_meta`; nothing is kept between requests.
7
+ * - Clients on an earlier revision open with `initialize`; the handler answers it and serves
8
+ * them the same way, without a session.
9
+ *
10
+ * Every call runs through the kernel as a host call does: the profile's gates, guardrails and
11
+ * trace. The client reads what a model would: the call's guarded text, never its raw output.
12
+ *
13
+ * @module
14
+ */
15
+ import { invokeTool, labelText, registerProfile, } from '@theoremjs/agents';
16
+ import { caughtStatus, clientError } from '@theoremjs/agents/host';
17
+ import { hostInterface } from "../client/host-transport.js";
18
+ import { defineHostProfile, hostTool } from "./handler.js";
19
+ import { boundedRequestJson, requestByteLimit } from "./request-body.js";
20
+ /** The revision that carries its version on every request. */
21
+ const MODERN_VERSION = '2026-07-28';
22
+ /** Revisions that open with `initialize`, newest first. */
23
+ const LEGACY_VERSIONS = ['2025-11-25', '2025-06-18', '2025-03-26'];
24
+ const SUPPORTED_VERSIONS = [MODERN_VERSION, ...LEGACY_VERSIONS];
25
+ const META_VERSION = 'io.modelcontextprotocol/protocolVersion';
26
+ const META_CAPABILITIES = 'io.modelcontextprotocol/clientCapabilities';
27
+ const META_SERVER = 'io.modelcontextprotocol/serverInfo';
28
+ /**
29
+ * How long a current-revision client may keep each cacheable result, in milliseconds. Neither
30
+ * holds anything of the caller's, so any cache may share them.
31
+ */
32
+ const CACHED = {
33
+ 'server/discover': 3_600_000,
34
+ 'tools/list': 300_000,
35
+ };
36
+ const PARSE_ERROR = -32700;
37
+ const INVALID_REQUEST = -32600;
38
+ const METHOD_NOT_FOUND = -32601;
39
+ const INVALID_PARAMS = -32602;
40
+ const INTERNAL_ERROR = -32603;
41
+ const HEADER_MISMATCH = -32020;
42
+ const UNSUPPORTED_VERSION = -32022;
43
+ class RpcError extends Error {
44
+ status;
45
+ code;
46
+ data;
47
+ constructor(status, code, message, data) {
48
+ super(message);
49
+ this.status = status;
50
+ this.code = code;
51
+ this.data = data;
52
+ }
53
+ }
54
+ function reply(status, body) {
55
+ return new Response(JSON.stringify(body), {
56
+ status,
57
+ headers: { 'content-type': 'application/json', 'cache-control': 'no-store' },
58
+ });
59
+ }
60
+ function rpcError(id, error) {
61
+ return reply(error.status, {
62
+ jsonrpc: '2.0',
63
+ id,
64
+ error: {
65
+ code: error.code,
66
+ message: error.message,
67
+ ...(error.data !== undefined ? { data: error.data } : {}),
68
+ },
69
+ });
70
+ }
71
+ function isRecord(value) {
72
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
73
+ }
74
+ /** The JSON-RPC message in `body`; one message per POST, never a batch or a response. */
75
+ function rpcRequest(body) {
76
+ if (!isRecord(body) ||
77
+ body.jsonrpc !== '2.0' ||
78
+ typeof body.method !== 'string' ||
79
+ (body.id !== undefined && typeof body.id !== 'string' && typeof body.id !== 'number') ||
80
+ (body.params !== undefined && !isRecord(body.params)))
81
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
82
+ throw new RpcError(400, INVALID_REQUEST, 'Invalid request');
83
+ return {
84
+ ...(body.id !== undefined ? { id: body.id } : {}),
85
+ method: body.method,
86
+ params: isRecord(body.params) ? body.params : {},
87
+ };
88
+ }
89
+ /** A mirrored header's value, decoded from the `=?base64?…?=` form MCP uses for unsafe values. */
90
+ function headerValue(value) {
91
+ const encoded = /^=\?base64\?(.*)\?=$/.exec(value);
92
+ if (!encoded)
93
+ return value;
94
+ const bytes = Uint8Array.from(atob(encoded[1]), (char) => char.charCodeAt(0));
95
+ return new TextDecoder().decode(bytes);
96
+ }
97
+ /**
98
+ * The current revision's request checks: a version this server speaks, the capabilities the
99
+ * client declares, and headers that agree with the body they mirror.
100
+ */
101
+ function checkModern(request, message, meta) {
102
+ const version = meta[META_VERSION];
103
+ if (version !== MODERN_VERSION)
104
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
105
+ throw new RpcError(400, UNSUPPORTED_VERSION, 'Unsupported protocol version', {
106
+ supported: SUPPORTED_VERSIONS,
107
+ requested: version,
108
+ });
109
+ if (!isRecord(meta[META_CAPABILITIES]))
110
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
111
+ throw new RpcError(400, INVALID_PARAMS, `Missing _meta field ${META_CAPABILITIES}`);
112
+ const mirrored = [
113
+ ['mcp-protocol-version', version],
114
+ ['mcp-method', message.method],
115
+ ];
116
+ if (message.method === 'tools/call')
117
+ mirrored.push(['mcp-name', message.params.name]);
118
+ for (const [name, body] of mirrored) {
119
+ const header = request.headers.get(name);
120
+ if (header === null || headerValue(header) !== body)
121
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
122
+ throw new RpcError(400, HEADER_MISMATCH, `Header mismatch: ${name}`);
123
+ }
124
+ }
125
+ /** The legacy revision a client asked for in `initialize`, or the newest one this server speaks. */
126
+ function legacyVersion(params) {
127
+ const asked = params.protocolVersion;
128
+ return LEGACY_VERSIONS.find((version) => version === asked) ?? LEGACY_VERSIONS[0];
129
+ }
130
+ /** What the client reads of one call, from the kernel's events for it. */
131
+ function callResult(events, labels) {
132
+ for (const event of events) {
133
+ if (event.type === 'error')
134
+ return {
135
+ content: [
136
+ { type: 'text', text: event.error ?? labelText(`error.${event.errorKind}`, {}, labels) },
137
+ ],
138
+ isError: true,
139
+ };
140
+ if (event.type !== 'tool' || !('phase' in event.tool))
141
+ continue;
142
+ const tool = event.tool;
143
+ if (tool.phase === 'complete')
144
+ return {
145
+ content: [
146
+ { type: 'text', text: tool.readBack ?? '' },
147
+ ...(tool.parts ?? []).map(partContent),
148
+ ],
149
+ isError: false,
150
+ };
151
+ if (tool.phase === 'error')
152
+ return {
153
+ content: [{ type: 'text', text: tool.readBack ?? tool.failure.message }],
154
+ isError: true,
155
+ };
156
+ // why: No one can answer a gate here; the client reads why the call stopped, as a model would.
157
+ if (tool.phase === 'gate')
158
+ return {
159
+ content: [{ type: 'text', text: tool.readBack ?? labelText('error.blocked', {}, labels) }],
160
+ isError: true,
161
+ };
162
+ }
163
+ return {
164
+ content: [{ type: 'text', text: labelText('error.internal', {}, labels) }],
165
+ isError: true,
166
+ };
167
+ }
168
+ function partContent(part, index) {
169
+ if (part.type === 'text')
170
+ return { type: 'text', text: part.text };
171
+ if ('uri' in part)
172
+ return { type: 'resource_link', uri: part.uri, name: part.uri, mimeType: part.mimeType };
173
+ if (part.type === 'image' || part.type === 'audio')
174
+ return { type: part.type, data: part.data, mimeType: part.mimeType };
175
+ return {
176
+ type: 'resource',
177
+ resource: { uri: `part:${index}`, mimeType: part.mimeType, blob: part.data },
178
+ };
179
+ }
180
+ /** Same origin, or one the host lists; a request with no `Origin` is not from a page. */
181
+ function originAllowed(request, origins) {
182
+ const origin = request.headers.get('origin');
183
+ return origin === null || origin === new URL(request.url).origin || origins.includes(origin);
184
+ }
185
+ /**
186
+ * Serves a host profile's tools to MCP clients. Every tool the profile allows must run
187
+ * without a person's approval: an MCP client has no one to ask.
188
+ */
189
+ export function createTheoremMcpHandler(options) {
190
+ const host = defineHostProfile('createTheoremMcpHandler', options.profile);
191
+ for (const name of host.tools.allow) {
192
+ const permission = hostTool(name)?.permission;
193
+ if (permission !== undefined && permission !== 'auto') {
194
+ // labels-exempt: builder config error at setup; no user sees it
195
+ throw new Error(`createTheoremMcpHandler serves tools that run without approval; '${name}' has permission '${permission}'.`);
196
+ }
197
+ }
198
+ const maxBytes = requestByteLimit(options.maxRequestBytes);
199
+ registerProfile(host);
200
+ const serverInfo = { name: options.name ?? host.id, version: options.version };
201
+ const instructions = options.instructions ? { instructions: options.instructions } : {};
202
+ const listed = () => hostInterface(host, hostTool).tools.map((tool) => ({
203
+ name: tool.name,
204
+ description: tool.description,
205
+ inputSchema: tool.inputSchema,
206
+ annotations: {
207
+ readOnlyHint: tool.access === 'read-only',
208
+ ...(tool.access === 'destructive' ? { destructiveHint: true } : {}),
209
+ },
210
+ }));
211
+ async function call(request, params) {
212
+ const name = params.name;
213
+ if (typeof name !== 'string' || !listed().some((tool) => tool.name === name))
214
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
215
+ throw new RpcError(200, INVALID_PARAMS, `Unknown tool: ${String(name)}`);
216
+ const events = [];
217
+ for await (const event of invokeTool({
218
+ profile: host.id,
219
+ name,
220
+ input: params.arguments ?? {},
221
+ signal: request.signal,
222
+ host: await options.host?.(request),
223
+ }))
224
+ events.push(event);
225
+ return callResult(events, host.labels);
226
+ }
227
+ async function result(request, message, modern) {
228
+ switch (message.method) {
229
+ case 'server/discover':
230
+ if (!modern)
231
+ break;
232
+ return {
233
+ supportedVersions: SUPPORTED_VERSIONS,
234
+ capabilities: { tools: {} },
235
+ ...instructions,
236
+ };
237
+ case 'initialize':
238
+ if (modern)
239
+ break;
240
+ return {
241
+ protocolVersion: legacyVersion(message.params),
242
+ capabilities: { tools: {} },
243
+ serverInfo,
244
+ ...instructions,
245
+ };
246
+ case 'ping':
247
+ if (modern)
248
+ break;
249
+ return {};
250
+ case 'tools/list':
251
+ return { tools: listed() };
252
+ case 'tools/call':
253
+ return await call(request, message.params);
254
+ }
255
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
256
+ throw new RpcError(modern ? 404 : 200, METHOD_NOT_FOUND, `Method not found: ${message.method}`);
257
+ }
258
+ return async (request) => {
259
+ if (!originAllowed(request, options.origins ?? []))
260
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
261
+ return rpcError(null, new RpcError(403, INVALID_REQUEST, 'Origin not allowed'));
262
+ if (request.method !== 'POST')
263
+ return new Response(null, { status: 405, headers: { allow: 'POST' } });
264
+ let id = null;
265
+ try {
266
+ let body;
267
+ try {
268
+ body = await boundedRequestJson(request, maxBytes);
269
+ }
270
+ catch (err) {
271
+ if (err instanceof SyntaxError)
272
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
273
+ throw new RpcError(400, PARSE_ERROR, 'Parse error');
274
+ throw err;
275
+ }
276
+ const message = rpcRequest(body);
277
+ id = message.id ?? null;
278
+ const meta = isRecord(message.params._meta) ? message.params._meta : {};
279
+ const modern = META_VERSION in meta;
280
+ if (modern)
281
+ checkModern(request, message, meta);
282
+ else if (request.headers.get('mcp-protocol-version') === MODERN_VERSION)
283
+ // labels-exempt: protocol diagnostic for the MCP client, not end-user copy
284
+ throw new RpcError(400, INVALID_PARAMS, `Missing _meta field ${META_VERSION}`);
285
+ // invariant: A notification has no reply; the client needs only to know it arrived.
286
+ if (message.id === undefined)
287
+ return new Response(null, { status: 202 });
288
+ const answered = await result(request, message, modern);
289
+ const ttlMs = CACHED[message.method];
290
+ return reply(200, {
291
+ jsonrpc: '2.0',
292
+ id: message.id,
293
+ result: modern
294
+ ? {
295
+ resultType: 'complete',
296
+ ...answered,
297
+ ...(ttlMs !== undefined ? { ttlMs, cacheScope: 'public' } : {}),
298
+ _meta: { [META_SERVER]: serverInfo },
299
+ }
300
+ : answered,
301
+ });
302
+ }
303
+ catch (err) {
304
+ if (err instanceof RpcError)
305
+ return rpcError(id, err);
306
+ options.onError?.(err, { request });
307
+ const failure = clientError(err, host.labels, options.clientEvents);
308
+ const code = failure.errorKind === 'request' ? INVALID_REQUEST : INTERNAL_ERROR;
309
+ return rpcError(id, new RpcError(caughtStatus(err), code, failure.error));
310
+ }
311
+ };
312
+ }
package/server/mod.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Server half of `@theoremjs/react`: serve one profile to the chat UI, a
3
- * decision profile to the decision UI, or a host profile's tools to the host UI.
3
+ * decision profile to the decision UI, or a host profile's tools to the host UI or to MCP clients.
4
4
  *
5
5
  * @module
6
6
  */
@@ -13,6 +13,7 @@ export { createMemoryCredentialStore, type TheoremCredentialStore, type TheoremC
13
13
  export { createTheoremDecisionHandler, type TheoremDecisionHandlerOptions, } from './decision-handler.ts';
14
14
  export { createTheoremEmbeddingHandler, embedBodySchema, embedRequest, type TheoremEmbeddingHandlerOptions, } from './embedding-handler.ts';
15
15
  export { createTheoremHandler, createTheoremHostHandler, readBody, type TheoremHandlerOptions, type TheoremHostHandlerOptions, type TheoremRequestContext, theoremSession, theoremSessionId, } from './handler.ts';
16
+ export { createTheoremMcpHandler, type TheoremMcpHandlerOptions } from './mcp-handler.ts';
16
17
  export { createMemoryPageToolBroker, type PageToolBroker, type PageToolResult, pageToolResultSchema, sessionPageDispatch, } from './page-dispatch.ts';
17
18
  export { checkRequest, parseLiveClientMessage, parseLiveOpenMessage } from './request-check.ts';
18
19
  export { sessionStateStore } from './session-authority.ts';
package/server/mod.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Server half of `@theoremjs/react`: serve one profile to the chat UI, a
3
- * decision profile to the decision UI, or a host profile's tools to the host UI.
3
+ * decision profile to the decision UI, or a host profile's tools to the host UI or to MCP clients.
4
4
  *
5
5
  * @module
6
6
  */
@@ -12,6 +12,7 @@ export { createMemoryCredentialStore, } from "./credential-store.js";
12
12
  export { createTheoremDecisionHandler, } from "./decision-handler.js";
13
13
  export { createTheoremEmbeddingHandler, embedBodySchema, embedRequest, } from "./embedding-handler.js";
14
14
  export { createTheoremHandler, createTheoremHostHandler, readBody, theoremSession, theoremSessionId, } from "./handler.js";
15
+ export { createTheoremMcpHandler } from "./mcp-handler.js";
15
16
  export { createMemoryPageToolBroker, pageToolResultSchema, sessionPageDispatch, } from "./page-dispatch.js";
16
17
  export { checkRequest, parseLiveClientMessage, parseLiveOpenMessage } from "./request-check.js";
17
18
  export { sessionStateStore } from "./session-authority.js";