@creature-ai/sdk 0.1.6 → 0.1.8

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.
@@ -13401,7 +13401,7 @@ import path from "path";
13401
13401
 
13402
13402
  // src/vite/index.ts
13403
13403
  import { resolve, join, relative } from "path";
13404
- import { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync } from "fs";
13404
+ import { readdirSync, statSync, existsSync, writeFileSync, mkdirSync, rmSync, readFileSync } from "fs";
13405
13405
  import { createServer as createNetServer } from "net";
13406
13406
  import { createServer as createHttpServer } from "http";
13407
13407
  import { createHash } from "crypto";
@@ -13554,8 +13554,15 @@ function loadHtml(filePath, basePath) {
13554
13554
  <p>Run <code>npm run build</code> to build the UI.</p>
13555
13555
  </body></html>`;
13556
13556
  }
13557
- function htmlLoader(filePath, basePath) {
13558
- return () => loadHtml(filePath, basePath);
13557
+ function isHtmlContent(str) {
13558
+ const trimmed = str.trimStart();
13559
+ return trimmed.startsWith("<") || trimmed.toLowerCase().startsWith("<!doctype");
13560
+ }
13561
+ function htmlLoader(htmlOrPath, basePath) {
13562
+ if (isHtmlContent(htmlOrPath)) {
13563
+ return () => htmlOrPath;
13564
+ }
13565
+ return () => loadHtml(htmlOrPath, basePath);
13559
13566
  }
13560
13567
  function isInitializeRequest2(body) {
13561
13568
  if (typeof body !== "object" || body === null) return false;
@@ -13758,8 +13765,6 @@ var App = class {
13758
13765
  callerDir;
13759
13766
  shutdownRegistered = false;
13760
13767
  isShuttingDown = false;
13761
- resourceCache = /* @__PURE__ */ new Map();
13762
- resourceCacheConfig;
13763
13768
  /** Server-side instance state, keyed by instanceId. */
13764
13769
  instanceState = /* @__PURE__ */ new Map();
13765
13770
  /** Callbacks to invoke when an instance is destroyed. */
@@ -13775,11 +13780,6 @@ var App = class {
13775
13780
  this.callerDir = callerDir || process.cwd();
13776
13781
  this.config = config;
13777
13782
  this.isDev = config.dev ?? process.env.NODE_ENV === "development";
13778
- this.resourceCacheConfig = {
13779
- maxSize: config.resourceCache?.maxSize ?? 100,
13780
- ttlMs: config.resourceCache?.ttlMs ?? 0,
13781
- enabled: config.resourceCache?.enabled ?? true
13782
- };
13783
13783
  if (this.isDev && config.hmrPort) {
13784
13784
  this.hmrPort = config.hmrPort;
13785
13785
  }
@@ -13977,6 +13977,46 @@ var App = class {
13977
13977
  getTransportSessionCount() {
13978
13978
  return this.transports.size;
13979
13979
  }
13980
+ // ==========================================================================
13981
+ // Public API: Serverless Adapters
13982
+ // ==========================================================================
13983
+ /**
13984
+ * Get the app configuration.
13985
+ */
13986
+ getConfig() {
13987
+ return this.config;
13988
+ }
13989
+ /**
13990
+ * Get all tool definitions.
13991
+ */
13992
+ getToolDefinitions() {
13993
+ return this.tools;
13994
+ }
13995
+ /**
13996
+ * Get all resource definitions.
13997
+ */
13998
+ getResourceDefinitions() {
13999
+ return this.resources;
14000
+ }
14001
+ /**
14002
+ * Create a Vercel MCP adapter configuration.
14003
+ * For use with Vercel's `mcp-handler` package.
14004
+ *
14005
+ * Note: This returns a configuration callback. The implementation
14006
+ * is inline to avoid circular dependencies.
14007
+ */
14008
+ toVercelMcp(options) {
14009
+ return this.createServerlessConfig(options);
14010
+ }
14011
+ /**
14012
+ * Create an AWS Lambda handler.
14013
+ *
14014
+ * Note: This returns a Lambda handler function. The implementation
14015
+ * is inline to avoid circular dependencies.
14016
+ */
14017
+ toAwsLambda(options) {
14018
+ return this.createLambdaHandler(options);
14019
+ }
13980
14020
  /**
13981
14021
  * Close a specific transport session.
13982
14022
  */
@@ -13993,36 +14033,245 @@ var App = class {
13993
14033
  // ==========================================================================
13994
14034
  // Public API: Resource Cache
13995
14035
  // ==========================================================================
14036
+ // Private: Configuration Helpers
14037
+ // ==========================================================================
14038
+ getPort() {
14039
+ return this.config.port || parseInt(process.env.MCP_PORT || process.env.PORT || "3000", 10);
14040
+ }
14041
+ getCallerDir() {
14042
+ return this.callerDir;
14043
+ }
14044
+ // ==========================================================================
14045
+ // Private: Serverless Adapter Helpers
14046
+ // ==========================================================================
13996
14047
  /**
13997
- * Clear all cached resource content.
14048
+ * Create a ToolContext for serverless environments.
14049
+ * Shared between Vercel and Lambda adapters to avoid duplication.
13998
14050
  */
13999
- clearResourceCache() {
14000
- this.resourceCache.clear();
14001
- }
14002
14051
  /**
14003
- * Clear a specific resource from the cache.
14052
+ * In-memory state for serverless when no external adapter provided.
14053
+ * Only useful for local development - won't persist across invocations in production.
14004
14054
  */
14005
- clearResourceCacheEntry(uri) {
14006
- return this.resourceCache.delete(uri);
14055
+ serverlessMemoryState = /* @__PURE__ */ new Map();
14056
+ serverlessStateWarningLogged = false;
14057
+ serverlessRealtimeWarningLogged = false;
14058
+ createServerlessContext({
14059
+ instanceId,
14060
+ creatureToken,
14061
+ stateAdapter,
14062
+ realtimeAdapter
14063
+ }) {
14064
+ const websocketUrl = realtimeAdapter?.getWebSocketUrl?.(instanceId);
14065
+ const app = this;
14066
+ return {
14067
+ instanceId,
14068
+ creatureToken,
14069
+ getState: () => {
14070
+ if (stateAdapter) {
14071
+ return void 0;
14072
+ }
14073
+ if (!app.serverlessStateWarningLogged) {
14074
+ console.warn("[MCP] Using in-memory state - won't persist in production serverless");
14075
+ app.serverlessStateWarningLogged = true;
14076
+ }
14077
+ return app.serverlessMemoryState.get(instanceId);
14078
+ },
14079
+ setState: (state) => {
14080
+ if (stateAdapter) {
14081
+ stateAdapter.set(instanceId, state);
14082
+ return;
14083
+ }
14084
+ if (!app.serverlessStateWarningLogged) {
14085
+ console.warn("[MCP] Using in-memory state - won't persist in production serverless");
14086
+ app.serverlessStateWarningLogged = true;
14087
+ }
14088
+ app.serverlessMemoryState.set(instanceId, state);
14089
+ },
14090
+ send: (message) => {
14091
+ if (realtimeAdapter) {
14092
+ realtimeAdapter.send(instanceId, message);
14093
+ return;
14094
+ }
14095
+ if (!app.serverlessRealtimeWarningLogged) {
14096
+ console.warn("[MCP] Realtime disabled - provide realtimeAdapter for production");
14097
+ app.serverlessRealtimeWarningLogged = true;
14098
+ }
14099
+ },
14100
+ onMessage: (_handler) => {
14101
+ if (realtimeAdapter) {
14102
+ realtimeAdapter.subscribe(instanceId, _handler);
14103
+ return;
14104
+ }
14105
+ if (!app.serverlessRealtimeWarningLogged) {
14106
+ console.warn("[MCP] Realtime disabled - provide realtimeAdapter for production");
14107
+ app.serverlessRealtimeWarningLogged = true;
14108
+ }
14109
+ },
14110
+ onConnect: (_handler) => {
14111
+ if (realtimeAdapter) {
14112
+ return;
14113
+ }
14114
+ },
14115
+ websocketUrl
14116
+ };
14007
14117
  }
14008
14118
  /**
14009
- * Get resource cache statistics.
14119
+ * Format tool result for serverless response.
14120
+ * Shared between Vercel and Lambda adapters.
14010
14121
  */
14011
- getResourceCacheStats() {
14122
+ formatServerlessResult(result, instanceId, websocketUrl) {
14123
+ const text = result.text || JSON.stringify(result.data || {});
14012
14124
  return {
14013
- size: this.resourceCache.size,
14014
- maxSize: this.resourceCacheConfig.maxSize,
14015
- enabled: this.resourceCacheConfig.enabled
14125
+ content: [{ type: "text", text }],
14126
+ structuredContent: {
14127
+ ...result.data,
14128
+ instanceId,
14129
+ ...websocketUrl && { websocketUrl }
14130
+ },
14131
+ _meta: { "openai/widgetSessionId": instanceId },
14132
+ ...result.isError && { isError: true }
14016
14133
  };
14017
14134
  }
14018
- // ==========================================================================
14019
- // Private: Configuration Helpers
14020
- // ==========================================================================
14021
- getPort() {
14022
- return this.config.port || parseInt(process.env.MCP_PORT || process.env.PORT || "3000", 10);
14135
+ /**
14136
+ * Create a Vercel MCP configuration callback.
14137
+ * Returns a function compatible with Vercel's mcp-handler createMcpHandler.
14138
+ */
14139
+ createServerlessConfig(options) {
14140
+ const { stateAdapter, realtimeAdapter } = options || {};
14141
+ const app = this;
14142
+ return function mcpConfig(server, _context) {
14143
+ for (const [name, definition] of app.tools) {
14144
+ const { config: toolConfig, handler } = definition;
14145
+ const inputSchema = toolConfig.input || z2.object({});
14146
+ const hasUi = !!toolConfig.ui;
14147
+ server.registerTool(
14148
+ name,
14149
+ {
14150
+ title: name,
14151
+ description: toolConfig.description + (hasUi ? " Returns instanceId for the widget." : ""),
14152
+ inputSchema
14153
+ },
14154
+ async (args) => {
14155
+ try {
14156
+ const creatureToken = args._creatureToken;
14157
+ const { _creatureToken: _, ...cleanArgs } = args;
14158
+ const input = toolConfig.input ? toolConfig.input.parse(cleanArgs) : cleanArgs;
14159
+ const instanceId = cleanArgs.instanceId || app.generateInstanceId();
14160
+ const context = app.createServerlessContext({ instanceId, creatureToken, stateAdapter, realtimeAdapter });
14161
+ const result = await handler(input, context);
14162
+ return app.formatServerlessResult(result, instanceId, context.websocketUrl);
14163
+ } catch (error) {
14164
+ const err = error instanceof Error ? error : new Error(String(error));
14165
+ return { content: [{ type: "text", text: err.message }], isError: true };
14166
+ }
14167
+ }
14168
+ );
14169
+ }
14170
+ };
14023
14171
  }
14024
- getCallerDir() {
14025
- return this.callerDir;
14172
+ /**
14173
+ * Create an AWS Lambda handler function.
14174
+ */
14175
+ createLambdaHandler(options) {
14176
+ const { stateAdapter, realtimeAdapter } = options || {};
14177
+ const app = this;
14178
+ return async (event, _lambdaContext) => {
14179
+ const corsHeaders = {
14180
+ "Access-Control-Allow-Origin": "*",
14181
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
14182
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Mcp-Session-Id",
14183
+ "Content-Type": "application/json"
14184
+ };
14185
+ if (event.httpMethod === "OPTIONS") {
14186
+ return { statusCode: 204, headers: corsHeaders, body: "" };
14187
+ }
14188
+ let body = {};
14189
+ if (event.body) {
14190
+ try {
14191
+ body = JSON.parse(
14192
+ event.isBase64Encoded ? Buffer.from(event.body, "base64").toString() : event.body
14193
+ );
14194
+ } catch {
14195
+ return {
14196
+ statusCode: 400,
14197
+ headers: corsHeaders,
14198
+ body: JSON.stringify({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error" }, id: null })
14199
+ };
14200
+ }
14201
+ }
14202
+ try {
14203
+ const method = body.method;
14204
+ const params = body.params;
14205
+ const id = body.id;
14206
+ if (method === "initialize") {
14207
+ return {
14208
+ statusCode: 200,
14209
+ headers: corsHeaders,
14210
+ body: JSON.stringify({
14211
+ jsonrpc: "2.0",
14212
+ result: {
14213
+ protocolVersion: "2024-11-05",
14214
+ serverInfo: { name: app.config.name, version: app.config.version },
14215
+ capabilities: { tools: {}, resources: {} }
14216
+ },
14217
+ id
14218
+ })
14219
+ };
14220
+ }
14221
+ if (method === "tools/list") {
14222
+ const tools = [];
14223
+ for (const [name, definition] of app.tools) {
14224
+ tools.push({
14225
+ name,
14226
+ description: definition.config.description,
14227
+ inputSchema: { type: "object" }
14228
+ });
14229
+ }
14230
+ return {
14231
+ statusCode: 200,
14232
+ headers: corsHeaders,
14233
+ body: JSON.stringify({ jsonrpc: "2.0", result: { tools }, id })
14234
+ };
14235
+ }
14236
+ if (method === "tools/call") {
14237
+ const toolName = params?.name;
14238
+ const args = params?.arguments || {};
14239
+ const definition = app.tools.get(toolName);
14240
+ if (!definition) {
14241
+ return {
14242
+ statusCode: 200,
14243
+ headers: corsHeaders,
14244
+ body: JSON.stringify({ jsonrpc: "2.0", error: { code: -32601, message: `Tool not found: ${toolName}` }, id })
14245
+ };
14246
+ }
14247
+ const { config: toolConfig, handler } = definition;
14248
+ const creatureToken = args._creatureToken;
14249
+ const { _creatureToken: _, ...cleanArgs } = args;
14250
+ const input = toolConfig.input ? toolConfig.input.parse(cleanArgs) : cleanArgs;
14251
+ const instanceId = cleanArgs.instanceId || app.generateInstanceId();
14252
+ const context = app.createServerlessContext({ instanceId, creatureToken, stateAdapter, realtimeAdapter });
14253
+ const result = await handler(input, context);
14254
+ const formatted = app.formatServerlessResult(result, instanceId, context.websocketUrl);
14255
+ return {
14256
+ statusCode: 200,
14257
+ headers: corsHeaders,
14258
+ body: JSON.stringify({ jsonrpc: "2.0", result: formatted, id })
14259
+ };
14260
+ }
14261
+ return {
14262
+ statusCode: 200,
14263
+ headers: corsHeaders,
14264
+ body: JSON.stringify({ jsonrpc: "2.0", error: { code: -32601, message: `Method not found: ${method}` }, id })
14265
+ };
14266
+ } catch (error) {
14267
+ const err = error instanceof Error ? error : new Error(String(error));
14268
+ return {
14269
+ statusCode: 500,
14270
+ headers: corsHeaders,
14271
+ body: JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: err.message }, id: body.id ?? null })
14272
+ };
14273
+ }
14274
+ };
14026
14275
  }
14027
14276
  getHmrPort() {
14028
14277
  if (!this.isDev) return null;
@@ -14091,31 +14340,6 @@ var App = class {
14091
14340
  return true;
14092
14341
  }
14093
14342
  // ==========================================================================
14094
- // Private: Resource Cache
14095
- // ==========================================================================
14096
- getCachedResource(uri) {
14097
- if (!this.resourceCacheConfig.enabled) return null;
14098
- const cached = this.resourceCache.get(uri);
14099
- if (!cached) return null;
14100
- const { ttlMs } = this.resourceCacheConfig;
14101
- if (ttlMs > 0 && Date.now() - cached.timestamp > ttlMs) {
14102
- this.resourceCache.delete(uri);
14103
- return null;
14104
- }
14105
- return cached.html;
14106
- }
14107
- setCachedResource(uri, html) {
14108
- if (!this.resourceCacheConfig.enabled) return;
14109
- const { maxSize } = this.resourceCacheConfig;
14110
- if (this.resourceCache.size >= maxSize) {
14111
- const oldestKey = this.resourceCache.keys().next().value;
14112
- if (oldestKey) {
14113
- this.resourceCache.delete(oldestKey);
14114
- }
14115
- }
14116
- this.resourceCache.set(uri, { html, timestamp: Date.now() });
14117
- }
14118
- // ==========================================================================
14119
14343
  // Private: Express Server
14120
14344
  // ==========================================================================
14121
14345
  createExpressApp() {
@@ -14314,10 +14538,12 @@ var App = class {
14314
14538
  },
14315
14539
  async (args) => {
14316
14540
  try {
14317
- const input = config.input ? config.input.parse(args) : args;
14541
+ const creatureToken = args._creatureToken;
14542
+ const { _creatureToken: _, ...cleanArgs } = args;
14543
+ const input = config.input ? config.input.parse(cleanArgs) : cleanArgs;
14318
14544
  let instanceId;
14319
14545
  if (hasUi && config.ui) {
14320
- instanceId = this.resolveInstanceId(config.ui, args.instanceId);
14546
+ instanceId = this.resolveInstanceId(config.ui, cleanArgs.instanceId);
14321
14547
  }
14322
14548
  const resource = config.ui ? this.resources.get(config.ui) : void 0;
14323
14549
  const hasWebSocket = resource?.config.websocket === true;
@@ -14329,6 +14555,7 @@ var App = class {
14329
14555
  }
14330
14556
  const context = {
14331
14557
  instanceId: instanceId || "",
14558
+ creatureToken,
14332
14559
  getState: () => instanceId ? this.instanceState.get(instanceId) : void 0,
14333
14560
  setState: (state) => {
14334
14561
  if (instanceId) {
@@ -14398,6 +14625,11 @@ var App = class {
14398
14625
  if (config.completedMessage) {
14399
14626
  toolMeta["openai/toolInvocation/invoked"] = config.completedMessage;
14400
14627
  }
14628
+ if (this.config.auth?.creatureManaged) {
14629
+ toolMeta.creature = {
14630
+ auth: { managed: true }
14631
+ };
14632
+ }
14401
14633
  return toolMeta;
14402
14634
  }
14403
14635
  buildToolDescription(config, inputSchema) {
@@ -14541,13 +14773,100 @@ function wrapServer(server) {
14541
14773
  };
14542
14774
  return server;
14543
14775
  }
14776
+
14777
+ // src/server/auth.ts
14778
+ var CreatureTokenError = class extends Error {
14779
+ /** Error code from the verification API */
14780
+ code;
14781
+ constructor({ code, message }) {
14782
+ super(message);
14783
+ this.name = "CreatureTokenError";
14784
+ this.code = code;
14785
+ }
14786
+ };
14787
+ var DEFAULT_API_URL = "https://api.creature.run";
14788
+ var extractToken = (tokenOrHeader) => {
14789
+ const trimmed = tokenOrHeader.trim();
14790
+ if (trimmed.toLowerCase().startsWith("bearer ")) {
14791
+ return trimmed.slice(7).trim();
14792
+ }
14793
+ return trimmed;
14794
+ };
14795
+ var verifyCreatureToken = async (tokenOrHeader, config) => {
14796
+ if (!tokenOrHeader) {
14797
+ throw new CreatureTokenError({
14798
+ code: "missing_token",
14799
+ message: "No token provided. Expected Authorization header or raw token."
14800
+ });
14801
+ }
14802
+ const token = extractToken(tokenOrHeader);
14803
+ if (!token) {
14804
+ throw new CreatureTokenError({
14805
+ code: "missing_token",
14806
+ message: "Empty token after extraction from Authorization header."
14807
+ });
14808
+ }
14809
+ const apiUrl = config?.apiUrl || DEFAULT_API_URL;
14810
+ const verifyUrl = `${apiUrl}/apps/v1/token/verify`;
14811
+ let response;
14812
+ try {
14813
+ response = await fetch(verifyUrl, {
14814
+ method: "POST",
14815
+ headers: {
14816
+ Authorization: `Bearer ${token}`,
14817
+ "Content-Type": "application/json"
14818
+ }
14819
+ });
14820
+ } catch (err) {
14821
+ throw new CreatureTokenError({
14822
+ code: "network_error",
14823
+ message: `Failed to reach Creature API: ${err instanceof Error ? err.message : "Unknown error"}`
14824
+ });
14825
+ }
14826
+ if (!response.ok) {
14827
+ let errorData = {};
14828
+ try {
14829
+ errorData = await response.json();
14830
+ } catch {
14831
+ }
14832
+ throw new CreatureTokenError({
14833
+ code: errorData.error || "verification_failed",
14834
+ message: errorData.error_description || `Token verification failed (HTTP ${response.status})`
14835
+ });
14836
+ }
14837
+ let data;
14838
+ try {
14839
+ data = await response.json();
14840
+ } catch {
14841
+ throw new CreatureTokenError({
14842
+ code: "invalid_response",
14843
+ message: "Invalid JSON response from Creature API"
14844
+ });
14845
+ }
14846
+ if (!data.valid || !data.claims?.user) {
14847
+ throw new CreatureTokenError({
14848
+ code: "invalid_token",
14849
+ message: "Token is not valid"
14850
+ });
14851
+ }
14852
+ return {
14853
+ user: data.claims.user,
14854
+ organization: data.claims.organization,
14855
+ project: data.claims.project,
14856
+ session: data.claims.session,
14857
+ expiresAt: data.expires_at
14858
+ };
14859
+ };
14544
14860
  export {
14545
14861
  App,
14862
+ CreatureTokenError,
14546
14863
  MIME_TYPES,
14547
14864
  createApp,
14548
14865
  htmlLoader,
14866
+ isHtmlContent,
14549
14867
  loadHtml,
14550
14868
  svgToDataUri,
14869
+ verifyCreatureToken,
14551
14870
  wrapServer
14552
14871
  };
14553
14872
  //# sourceMappingURL=index.js.map