@cosmoops/connect-sdk 0.15.0 → 0.16.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +2 -1
  3. package/dist/cjs/client/components/ai-screens.js +73 -0
  4. package/dist/cjs/client/components/ai-widgets.js +37 -0
  5. package/dist/cjs/client/components/ask-ai.js +31 -5
  6. package/dist/cjs/client/index.js +5 -2
  7. package/dist/cjs/index.js +6 -1
  8. package/dist/cjs/mcp/index.js +90 -0
  9. package/dist/cjs/mcp/tools.js +369 -0
  10. package/dist/cjs/server/client.js +1 -1
  11. package/dist/cjs/server/handler.js +7 -0
  12. package/dist/cjs/server/resources/ai.js +8 -1
  13. package/dist/cjs/store-schema.js +66 -0
  14. package/dist/esm/client/components/ai-screens.js +69 -0
  15. package/dist/esm/client/components/ai-widgets.js +37 -1
  16. package/dist/esm/client/components/ask-ai.js +32 -6
  17. package/dist/esm/client/index.js +1 -0
  18. package/dist/esm/index.js +3 -0
  19. package/dist/esm/mcp/index.js +85 -0
  20. package/dist/esm/mcp/tools.js +366 -0
  21. package/dist/esm/server/client.js +1 -1
  22. package/dist/esm/server/handler.js +7 -0
  23. package/dist/esm/server/resources/ai.js +8 -1
  24. package/dist/esm/store-schema.js +62 -0
  25. package/dist/types/client/components/ai-screens.d.ts +25 -0
  26. package/dist/types/client/components/ai-widgets.d.ts +34 -1
  27. package/dist/types/client/components/ask-ai.d.ts +47 -1
  28. package/dist/types/client/index.d.ts +2 -0
  29. package/dist/types/index.d.ts +4 -0
  30. package/dist/types/mcp/index.d.ts +74 -0
  31. package/dist/types/mcp/tools.d.ts +107 -0
  32. package/dist/types/server/client.d.ts +1 -1
  33. package/dist/types/server/resources/ai.d.ts +11 -1
  34. package/dist/types/store-schema.d.ts +52 -0
  35. package/dist/types/types.d.ts +50 -1
  36. package/package.json +10 -1
@@ -0,0 +1,62 @@
1
+ import { storeIndexFor, storeIndexSignature } from "./store.js";
2
+ /**
3
+ * The signature of an index scanned backwards.
4
+ *
5
+ * Firestore serves a query from an index in either direction as long as *every*
6
+ * ordered field is flipped, so `[a asc, b desc]` also answers `[a desc, b asc]`.
7
+ * Mirrored by Connect's own `indexCovers`; kept in step with it deliberately,
8
+ * because a check here that is stricter than the server's would refuse queries
9
+ * that actually work, and a looser one would promise queries that do not.
10
+ */
11
+ function reversedIndexSignature(fields) {
12
+ return storeIndexSignature(fields.map((entry) => entry.direction === "array-contains"
13
+ ? entry
14
+ : { field: entry.field, direction: entry.direction === "asc" ? "desc" : "asc" }));
15
+ }
16
+ /** Whether one of `declared` can serve a query needing exactly `required`. */
17
+ export function storeIndexCovers(declared, required) {
18
+ const wanted = storeIndexSignature(required);
19
+ const mirrored = reversedIndexSignature(required);
20
+ return declared.some((index) => {
21
+ const signature = storeIndexSignature(index.fields);
22
+ return signature === wanted || signature === mirrored;
23
+ });
24
+ }
25
+ /**
26
+ * Why a collection cannot serve a query, in words a caller can act on - or
27
+ * `null` when it can.
28
+ *
29
+ * Runs the same two rules Connect runs, in the same order: every field must be
30
+ * declared queryable, and the shape must be inside an automatic single-field
31
+ * index or covered by a declared composite one. Checking here rather than
32
+ * letting the call fail is not about saving a round trip - it is so the refusal
33
+ * can name the index to declare while the caller still has the query in hand.
34
+ */
35
+ export function describeQueryRejection(collection, query) {
36
+ const filters = query.filters ?? [];
37
+ const orderBy = query.orderBy ?? null;
38
+ const queryable = new Set(collection.queryableFields);
39
+ const undeclared = [
40
+ ...new Set([...filters.map((filter) => filter.field), ...(orderBy ? [orderBy.field] : [])]),
41
+ ].filter((field) => !queryable.has(field));
42
+ if (undeclared.length > 0) {
43
+ return `"${collection.name}" cannot be filtered or ordered on ${undeclared.join(", ")}. Queryable fields are: ${collection.queryableFields.join(", ")}.`;
44
+ }
45
+ let required;
46
+ try {
47
+ required = storeIndexFor({ filters, orderBy });
48
+ }
49
+ catch (error) {
50
+ // Firestore cannot serve the shape at all - two `array-contains`, ranges
51
+ // over two fields, a range ordered by another field. No index would help,
52
+ // so the thrown message is the whole answer.
53
+ return error instanceof Error ? error.message : String(error);
54
+ }
55
+ if (!required)
56
+ return null;
57
+ if (storeIndexCovers(collection.indexes, required))
58
+ return null;
59
+ return `"${collection.name}" has no index for this query. It would need one over ${required
60
+ .map((entry) => `${entry.field} (${entry.direction})`)
61
+ .join(", ")}.`;
62
+ }
@@ -0,0 +1,25 @@
1
+ import type { ConnectScreen } from "../../types.js";
2
+ /**
3
+ * The declared screen a live path is on, or `null` if none matches.
4
+ *
5
+ * `null` is a normal outcome, not an error: an application need only describe
6
+ * the screens it wants the assistant to know about, and an undescribed one
7
+ * simply costs the assistant its orientation rather than costing the user an
8
+ * answer.
9
+ */
10
+ export declare function matchConnectScreen(screens: readonly ConnectScreen[], path: string): ConnectScreen | null;
11
+ /**
12
+ * Whether an href the model produced may be rendered as a link.
13
+ *
14
+ * Same reasoning as Connect's own `showActions` check, and the same conclusion:
15
+ * an assistant that can emit an arbitrary URL is a redirect the model can be
16
+ * talked into, and retrieved content shares a context window with its
17
+ * tool-calling authority. So a link is drawn only when it resolves to a screen
18
+ * *your application declared* - which makes an external destination structurally
19
+ * impossible, since no route pattern has a scheme or a host.
20
+ *
21
+ * The protocol-relative form is refused separately: `//evil.example.com` has no
22
+ * scheme for a path matcher to trip over, and would otherwise reach an anchor
23
+ * as an absolute destination.
24
+ */
25
+ export declare function isLinkableConnectScreen(screens: readonly ConnectScreen[], href: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- import type { AiChartInput, AiDetailsInput, AiMetricsInput, AiSourcesInput, AiStepsInput, AiTableInput, AiTimelineInput } from "../../types.js";
2
+ import type { AiChartInput, AiDetailsInput, AiMetricsInput, AiSourcesInput, AiStepsInput, AiActionsInput, AiTableInput, AiTimelineInput, ConnectScreen } from "../../types.js";
3
3
  /**
4
4
  * `showTable`. Sorting and CSV are what a table is *for* - once an answer is
5
5
  * tabular the next thing anyone wants is to reorder it or take it elsewhere,
@@ -40,3 +40,36 @@ export declare function AiSteps({ title, steps, renderDetail, }: Readonly<AiStep
40
40
  * without is still a named row - never a marker with nothing behind it.
41
41
  */
42
42
  export declare function AiSources({ sources }: Readonly<AiSourcesInput>): import("react").JSX.Element;
43
+ /**
44
+ * Where to go next, as links into *your* application.
45
+ *
46
+ * ## Why links and not navigation
47
+ *
48
+ * The obvious version of "the assistant can act" moves the user. It is also the
49
+ * version that breaks the property the whole design rests on: nothing happens
50
+ * without a click. Retrieved content shares a context window with the model's
51
+ * tool-calling authority, so an auto-navigate is a redirect primitive that
52
+ * content can reach. A link cannot be, and loses nothing - the user was going
53
+ * to click something anyway.
54
+ *
55
+ * ## Why every href is checked against your declared screens
56
+ *
57
+ * The model can emit any string. Only your application knows which strings are
58
+ * real routes, which is why `screens` is a prop rather than something Connect
59
+ * could supply. A link that does not resolve is *dropped* rather than rendered
60
+ * broken - the prose around it still stands, so a missing link reads as terse
61
+ * rather than wrong.
62
+ *
63
+ * `renderLink` is how this stays router-agnostic. Given one, a link is your
64
+ * framework's - a Next `<Link>`, a TanStack `<Link>` - and navigation is a
65
+ * client transition. Without one it falls back to an anchor, which works
66
+ * everywhere and costs a document load.
67
+ */
68
+ export declare function AiActions({ title, actions, screens, renderLink, }: Readonly<AiActionsInput & {
69
+ screens: readonly ConnectScreen[];
70
+ renderLink?: (props: {
71
+ href: string;
72
+ className: string;
73
+ children: ReactNode;
74
+ }) => ReactNode;
75
+ }>): import("react").JSX.Element | null;
@@ -1,5 +1,51 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type StreamdownProps } from "streamdown";
3
+ import type { ConnectScreen } from "../../types.js";
2
4
  export interface ConnectAskAiProps {
5
+ /**
6
+ * The screens in your application, described for the assistant.
7
+ *
8
+ * Without these it answers from general knowledge about software, because
9
+ * Connect has no idea what your product is - see `ConnectScreen`. With them,
10
+ * "what can I do here?" answers in your own words, the empty panel offers
11
+ * your own opening questions, and `showActions` can link into your routes.
12
+ *
13
+ * Prose and links only: Connect never uses them to decide what anyone may do,
14
+ * and never executes anything on your behalf.
15
+ */
16
+ screens?: readonly ConnectScreen[];
17
+ /**
18
+ * The route the user is currently on.
19
+ *
20
+ * **Usually omit this.** Left unset, the panel resolves the live path itself
21
+ * through `useActivePath`, which reads `location.pathname` from a
22
+ * `useSyncExternalStore` whose server snapshot is empty - so it works inside
23
+ * a shared App Shell, where `usePathname()` is refused outright
24
+ * (`CLIENT_HOOK_DYNAMIC`) because URL data cannot live in an artifact shared
25
+ * across URLs. That is the same reason `ConnectAppShell` recommends leaving
26
+ * its own `activePath` unset in a Next app.
27
+ *
28
+ * Pass it only where your router's path is not `location.pathname` - a
29
+ * hash router, or a panel embedded under a basePath your routes do not
30
+ * include.
31
+ */
32
+ activePath?: string;
33
+ /**
34
+ * Renders an action link with your router, so following one is a client
35
+ * transition rather than a document load. Omit it and links are plain
36
+ * anchors, which work everywhere.
37
+ *
38
+ * ```tsx
39
+ * renderLink={({ href, className, children }) => (
40
+ * <Link href={href} className={className}>{children}</Link>
41
+ * )}
42
+ * ```
43
+ */
44
+ renderLink?: (props: {
45
+ href: string;
46
+ className: string;
47
+ children: ReactNode;
48
+ }) => ReactNode;
3
49
  /** Panel heading. Defaults to `"Ask AI"`. */
4
50
  title?: string;
5
51
  /** Byline over each assistant turn. Defaults to `"Assistant"`. */
@@ -70,4 +116,4 @@ export interface ConnectAskAiProps {
70
116
  * Neither is something the browser can discover, so both surface as a message
71
117
  * in the panel rather than as a hidden button.
72
118
  */
73
- export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, }: ConnectAskAiProps): import("react").JSX.Element | null;
119
+ export declare function ConnectAskAi({ title, assistantName, placeholder, hint, defaultOpen, markdownPlugins, screens, activePath, renderLink, }: ConnectAskAiProps): import("react").JSX.Element | null;
@@ -41,6 +41,8 @@ export type { ConnectSidebarNavProps, ConnectSideNavProps } from "./components/s
41
41
  export { useActivePath } from "./use-active-path.js";
42
42
  export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemActive, gateExemptPaths, filterNavForAudience, } from "./components/nav.js";
43
43
  export type { ConnectNav, ConnectNavItem, ConnectNavSection, ConnectNavLinkProps, ConnectLinkRenderer, } from "./components/nav.js";
44
+ export type { ConnectScreen } from "../types.js";
45
+ export { matchConnectScreen, isLinkableConnectScreen, } from "./components/ai-screens.js";
44
46
  export { ConnectAppsGrid } from "./components/apps-grid.js";
45
47
  export type { ConnectAppsGridProps } from "./components/apps-grid.js";
46
48
  export { ConnectAskAi } from "./components/ask-ai.js";
@@ -10,10 +10,14 @@
10
10
  * Holds the API key. Never import this from browser code.
11
11
  * - `@cosmoops/connect-sdk/client` - React components and hooks. Talks to your
12
12
  * own origin only.
13
+ * - `@cosmoops/connect-sdk/mcp` - your store's declaration, derived into MCP
14
+ * tools. Holds the API key; never import this from browser code.
13
15
  */
14
16
  export { ConnectError, isConnectError } from "./errors.js";
15
17
  export { blockingBlockers, normalizePlanFeatures } from "./helpers.js";
16
18
  export { arrayRemove, arrayUnion, deleteField, documentExists, increment, serverTimestamp, storeIndexFor, storeIndexSignature, unchangedSince, } from "./store.js";
17
19
  export type { StoreIndexDirection, StoreIndexField } from "./store.js";
20
+ export { describeQueryRejection, storeIndexCovers } from "./store-schema.js";
21
+ export type { StoreCollectionDeclaration } from "./store-schema.js";
18
22
  export type { WirePlanFeature } from "./helpers.js";
19
23
  export type * from "./types.js";
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `@cosmoops/connect-sdk/mcp` - your application's store, as an MCP server.
3
+ *
4
+ * Server-only: every call goes out through `ConnectClient`, which holds the API
5
+ * key. The `browser` export condition resolves this entry to a stub, so an
6
+ * import from client code fails the build rather than shipping the key.
7
+ *
8
+ * ## Zero dependencies, on purpose
9
+ *
10
+ * This entry does not import `@modelcontextprotocol/sdk`, and the package takes
11
+ * no dependency on it. `listTools()` and `callTool()` answer in the shapes the
12
+ * MCP protocol defines, so wiring them to the official server is four lines and
13
+ * a transport you choose:
14
+ *
15
+ * ```ts
16
+ * const toolset = createStoreToolset({
17
+ * collections: STORE_SCHEMA,
18
+ * data: connect.data,
19
+ * workspaceId: session.workspaceId,
20
+ * asUser: session.sessionToken,
21
+ * })
22
+ *
23
+ * server.setRequestHandler(ListToolsRequestSchema, () => toolset.listTools())
24
+ * server.setRequestHandler(CallToolRequestSchema, (request) =>
25
+ * toolset.callTool(request.params.name, request.params.arguments ?? {}),
26
+ * )
27
+ * ```
28
+ *
29
+ * The alternative - depending on the MCP SDK so this file could return an
30
+ * `McpServer` - would put a version of it in every install of this package,
31
+ * including the ones that never touch MCP, and pin partners to whichever
32
+ * version shipped here. The protocol shapes are stable and small; the coupling
33
+ * would not be.
34
+ *
35
+ * ## One toolset per session
36
+ *
37
+ * `asUser` is an end user's Connect session token, and Connect checks each
38
+ * collection's declared permission against that member's live roles. A toolset
39
+ * is therefore scoped to one signed-in user - build it per request from
40
+ * `getConnectSession()`, or pass a function for `asUser` if the surrounding
41
+ * server outlives a session.
42
+ */
43
+ export { deriveStoreTools } from "./tools.js";
44
+ export type { JsonSchema, StoreDataPort, StoreTool, StoreToolWrites, StoreToolsetOptions, } from "./tools.js";
45
+ import { type StoreTool, type StoreToolsetOptions } from "./tools.js";
46
+ /** One entry of an MCP `tools/list` response. */
47
+ export interface McpToolDescriptor {
48
+ name: string;
49
+ title: string;
50
+ description: string;
51
+ inputSchema: Record<string, unknown>;
52
+ annotations: {
53
+ readOnlyHint: boolean;
54
+ destructiveHint: boolean;
55
+ idempotentHint: boolean;
56
+ };
57
+ }
58
+ /** An MCP `tools/call` result. */
59
+ export interface McpToolResult {
60
+ content: {
61
+ type: "text";
62
+ text: string;
63
+ }[];
64
+ isError?: boolean;
65
+ }
66
+ export interface StoreToolset {
67
+ /** The derived tools, if you would rather register them yourself. */
68
+ readonly tools: readonly StoreTool[];
69
+ listTools(): {
70
+ tools: McpToolDescriptor[];
71
+ };
72
+ callTool(name: string, args: Record<string, unknown>): Promise<McpToolResult>;
73
+ }
74
+ export declare function createStoreToolset(options: StoreToolsetOptions): StoreToolset;
@@ -0,0 +1,107 @@
1
+ import type { StoreAggregation, StoreDocument, StoreOrderBy, StoreQueryFilter } from "../types.js";
2
+ import { type StoreCollectionDeclaration } from "../store-schema.js";
3
+ /** A JSON Schema object, as the MCP `tools/list` response carries it. */
4
+ export type JsonSchema = Record<string, unknown>;
5
+ /**
6
+ * One derived tool: the wire definition, and the call behind it.
7
+ *
8
+ * Shaped so `listTools`/`callTool` can be answered without this module knowing
9
+ * what transport is in front of it - see the note on `createStoreToolset`.
10
+ */
11
+ export interface StoreTool {
12
+ name: string;
13
+ title: string;
14
+ description: string;
15
+ inputSchema: JsonSchema;
16
+ annotations: {
17
+ readOnlyHint: boolean;
18
+ destructiveHint: boolean;
19
+ idempotentHint: boolean;
20
+ };
21
+ execute(args: Record<string, unknown>): Promise<unknown>;
22
+ }
23
+ /**
24
+ * The subset of `ConnectClient["data"]` a toolset uses.
25
+ *
26
+ * Structural rather than the concrete class so this module pulls in neither the
27
+ * transport nor the key-holding client, and so a test can pass a recording
28
+ * double without a network seam of its own.
29
+ */
30
+ export interface StoreDataPort {
31
+ get(input: {
32
+ workspaceId?: string;
33
+ collection: string;
34
+ id: string;
35
+ asUser?: string;
36
+ }): Promise<StoreDocument>;
37
+ query(input: {
38
+ workspaceId?: string;
39
+ collection: string;
40
+ filters?: StoreQueryFilter[];
41
+ orderBy?: StoreOrderBy;
42
+ limit?: number;
43
+ cursor?: string | null;
44
+ asUser?: string;
45
+ }): Promise<{
46
+ items: StoreDocument[];
47
+ nextCursor: string | null;
48
+ }>;
49
+ aggregate(input: {
50
+ workspaceId?: string;
51
+ collection: string;
52
+ aggregations: StoreAggregation[];
53
+ filters?: StoreQueryFilter[];
54
+ asUser?: string;
55
+ }): Promise<{
56
+ values: Record<string, number | null>;
57
+ }>;
58
+ create(input: {
59
+ workspaceId?: string;
60
+ collection: string;
61
+ data: Record<string, unknown>;
62
+ id?: string;
63
+ asUser?: string;
64
+ }): Promise<StoreDocument>;
65
+ update(input: {
66
+ workspaceId?: string;
67
+ collection: string;
68
+ id: string;
69
+ data: Record<string, unknown>;
70
+ asUser?: string;
71
+ }): Promise<StoreDocument>;
72
+ delete(input: {
73
+ workspaceId?: string;
74
+ collection: string;
75
+ id: string;
76
+ asUser?: string;
77
+ }): Promise<void>;
78
+ }
79
+ /**
80
+ * How much of the write surface to derive.
81
+ *
82
+ * Defaults to `"none"`. A toolset is usually handed to a model driving somebody
83
+ * else's conversation, and the blast radius of a derived `delete` on every
84
+ * collection is not something to acquire by default - opting in is one word,
85
+ * and discovering it by accident is not recoverable.
86
+ */
87
+ export type StoreToolWrites = "none" | "create" | "all";
88
+ export interface StoreToolsetOptions {
89
+ /** The application's own declaration - the same array seeded to `Application.storeSchema`. */
90
+ collections: readonly StoreCollectionDeclaration[];
91
+ data: StoreDataPort;
92
+ /**
93
+ * The end user's Connect session token. Every call is made on their behalf.
94
+ *
95
+ * A function is resolved per call, for a toolset outliving one session.
96
+ */
97
+ asUser: string | (() => string | Promise<string>);
98
+ /** Omit for an application-scoped collection; required for a workspace-scoped one. */
99
+ workspaceId?: string;
100
+ writes?: StoreToolWrites;
101
+ /** Prepended to every tool name, for a server mounting more than one source. */
102
+ prefix?: string;
103
+ /** Collections to derive from. Defaults to every declared `data` collection. */
104
+ include?: readonly string[];
105
+ }
106
+ /** Every tool one declaration yields, in a stable order. */
107
+ export declare function deriveStoreTools(options: StoreToolsetOptions): StoreTool[];
@@ -109,4 +109,4 @@ export declare class ConnectClient {
109
109
  constructor(options: ConnectClientOptions);
110
110
  }
111
111
  /** Kept in step with `package.json` by `sdk.test.ts`. */
112
- export declare const SDK_VERSION = "0.14.0";
112
+ export declare const SDK_VERSION = "0.16.0";
@@ -1,5 +1,5 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { ChatMessage } from "../../types.js";
2
+ import type { ChatMessage, ConnectScreen } from "../../types.js";
3
3
  /**
4
4
  * RAG chat and structured extraction against a workspace's data.
5
5
  *
@@ -21,6 +21,16 @@ export declare class AiResource {
21
21
  workspaceId: string;
22
22
  messages: ChatMessage[];
23
23
  asUser?: string;
24
+ /**
25
+ * Your application's screens, so the assistant answers in your product's
26
+ * words and can link into it - see `ConnectScreen`. Prose and links only:
27
+ * Connect never uses these to decide what anyone may do.
28
+ */
29
+ screens?: readonly ConnectScreen[] | unknown;
30
+ /** Where the user is, as `{ pathname }`. Matched against `screens`. */
31
+ screenContext?: {
32
+ pathname?: string;
33
+ } | unknown;
24
34
  }, options?: {
25
35
  signal?: AbortSignal;
26
36
  }): Promise<Response>;
@@ -0,0 +1,52 @@
1
+ import type { StoreQueryFilter, StoreOrderBy } from "./types.js";
2
+ import { type StoreIndexField } from "./store.js";
3
+ /**
4
+ * One collection as an application declares it, and the helpers that read a
5
+ * declaration back.
6
+ *
7
+ * The declaration is the same shape Connect stores on `Application.storeSchema`
8
+ * and the same one `scripts/seed-*-application.ts` writes there, so an app that
9
+ * builds its schema from its own query matrix can hand the result straight to
10
+ * anything here without a second type of its own. Connect remains the
11
+ * authority - this is a local copy of what was declared, useful for deriving
12
+ * things *from* the declaration, never for deciding access.
13
+ *
14
+ * Isomorphic and dependency-free, like the rest of the root entry.
15
+ */
16
+ export interface StoreCollectionDeclaration {
17
+ name: string;
18
+ kind: "data" | "kv";
19
+ scope: "workspace" | "application";
20
+ /** Prose. Connect shows it in the Console; `deriveStoreTools` gives it to a model. */
21
+ description: string;
22
+ /** The per-user permission Connect checks when a call carries `asUser`. `null` means any member. */
23
+ readPermission: string | null;
24
+ writePermission: string | null;
25
+ /** Whether `data.scan` may read this collection across every workspace. */
26
+ crossWorkspace: boolean;
27
+ /** Create-only. Connect refuses a replace, a merge or a delete for every caller. */
28
+ appendOnly: boolean;
29
+ /** Nothing may be filtered, ordered or aggregated on a field not listed. */
30
+ queryableFields: string[];
31
+ indexes: {
32
+ fields: StoreIndexField[];
33
+ }[];
34
+ }
35
+ /** Whether one of `declared` can serve a query needing exactly `required`. */
36
+ export declare function storeIndexCovers(declared: readonly {
37
+ fields: StoreIndexField[];
38
+ }[], required: readonly StoreIndexField[]): boolean;
39
+ /**
40
+ * Why a collection cannot serve a query, in words a caller can act on - or
41
+ * `null` when it can.
42
+ *
43
+ * Runs the same two rules Connect runs, in the same order: every field must be
44
+ * declared queryable, and the shape must be inside an automatic single-field
45
+ * index or covered by a declared composite one. Checking here rather than
46
+ * letting the call fail is not about saving a round trip - it is so the refusal
47
+ * can name the index to declare while the caller still has the query in hand.
48
+ */
49
+ export declare function describeQueryRejection(collection: StoreCollectionDeclaration, query: {
50
+ filters?: readonly StoreQueryFilter[];
51
+ orderBy?: StoreOrderBy | null;
52
+ }): string | null;
@@ -898,8 +898,57 @@ export interface AiSourcesInput {
898
898
  snippet?: string;
899
899
  }[];
900
900
  }
901
+ export interface AiActionsInput {
902
+ title?: string;
903
+ actions: {
904
+ label: string;
905
+ href: string;
906
+ detail?: string;
907
+ }[];
908
+ }
901
909
  /** The tools this client knows how to draw. Anything else in the stream is ignored. */
902
- export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources";
910
+ export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources" | "showActions";
911
+ /**
912
+ * One screen in your application, described for the assistant.
913
+ *
914
+ * ## Why you declare these rather than Connect discovering them
915
+ *
916
+ * Connect has no idea what your app is. It cannot know that `/deals` is a
917
+ * pipeline, that a "lead" becomes a "contact", or that the thing your users
918
+ * call a quote lives under Settings. Without that, an assistant sitting inside
919
+ * your product answers from general knowledge about software - which is exactly
920
+ * how it reads.
921
+ *
922
+ * Declaring a screen is the cheapest possible fix: one line of prose per route,
923
+ * and questions like "what can I do here?" start answering in your product's
924
+ * own words.
925
+ *
926
+ * ## What Connect does and does not do with them
927
+ *
928
+ * They are **prose and links, and nothing else**. Connect puts your
929
+ * descriptions into the model's context so it can answer and so `showActions`
930
+ * can link to your screens. It never uses them to decide what anyone may do -
931
+ * your app's own authorization is untouched and unconsulted here - and it never
932
+ * executes anything on your behalf.
933
+ *
934
+ * They also arrive from a browser, so Connect treats them as untrusted text:
935
+ * bounded in count and length, and flattened to single lines before they reach
936
+ * a prompt.
937
+ */
938
+ export interface ConnectScreen {
939
+ /**
940
+ * The route, as your router writes it. Dynamic segments in brackets -
941
+ * `/deals/[dealId]` - so a live path with a real id still matches.
942
+ */
943
+ path: string;
944
+ /** One line: what a person does here, in your product's words. */
945
+ purpose: string;
946
+ /**
947
+ * Opening questions offered as chips on an empty conversation, phrased as
948
+ * your users would type them. Two or three; a fourth is a menu.
949
+ */
950
+ suggestions?: string[];
951
+ }
903
952
  /**
904
953
  * One document from the `data` store.
905
954
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmoops/connect-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Official server-side SDK for the CosmoOps Connect platform API - Login with Connect, entitlements, billing and metering.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",
@@ -31,6 +31,15 @@
31
31
  "import": "./dist/esm/next/index.js",
32
32
  "require": "./dist/cjs/next/index.js"
33
33
  },
34
+ "./mcp": {
35
+ "types": "./dist/types/mcp/index.d.ts",
36
+ "browser": {
37
+ "import": "./dist/esm/this-entry-is-server-only.js",
38
+ "require": "./dist/cjs/this-entry-is-server-only.js"
39
+ },
40
+ "import": "./dist/esm/mcp/index.js",
41
+ "require": "./dist/cjs/mcp/index.js"
42
+ },
34
43
  "./client": {
35
44
  "types": "./dist/types/client/index.d.ts",
36
45
  "import": "./dist/esm/client/index.js",