44reports-mcp 1.3.0 → 1.4.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.
@@ -151,7 +151,7 @@ export declare class ApiClient {
151
151
  }>;
152
152
  total: number;
153
153
  }>;
154
- getProduct(slug: string, domain?: string): Promise<{
154
+ getProduct(slug: string, domain?: string | string[]): Promise<{
155
155
  product: {
156
156
  id: string;
157
157
  slug: string;
@@ -173,11 +173,20 @@ export declare class ApiClient {
173
173
  domain: string;
174
174
  }>;
175
175
  }>;
176
- getProductFile(slug: string, filepath: string): Promise<{
176
+ getProductFile(slug: string, filepath: string, options?: {
177
+ format?: 'inline' | 'signed-url';
178
+ ttl?: number;
179
+ }): Promise<{
177
180
  path: string;
178
181
  content: unknown;
179
182
  content_type: string;
180
183
  size?: number;
184
+ } | {
185
+ path: string;
186
+ url: string;
187
+ expires_at: string;
188
+ content_type: string;
189
+ size: number;
181
190
  }>;
182
191
  updateProductFile(slug: string, filepath: string, body: {
183
192
  content: unknown;
@@ -233,7 +242,7 @@ export declare class ApiClient {
233
242
  deleteProductFile(slug: string, filepath: string): Promise<{
234
243
  success: boolean;
235
244
  }>;
236
- getProductIndex(slug: string, domain?: string): Promise<{
245
+ getProductIndex(slug: string, domain?: string | string[]): Promise<{
237
246
  product_slug: string;
238
247
  updated_at: string;
239
248
  files: Array<{
@@ -1,3 +1,13 @@
1
+ function withQuery(path, params) {
2
+ const qs = params.toString();
3
+ return qs ? `${path}?${qs}` : path;
4
+ }
5
+ function appendDomains(params, domain) {
6
+ if (!domain)
7
+ return;
8
+ for (const d of Array.isArray(domain) ? domain : [domain])
9
+ params.append('domain', d);
10
+ }
1
11
  export class ApiClient {
2
12
  config;
3
13
  constructor(config) {
@@ -102,15 +112,20 @@ export class ApiClient {
102
112
  params.set(key, String(value));
103
113
  }
104
114
  }
105
- const query = params.toString();
106
- return this.request(`/api/products${query ? `?${query}` : ''}`);
115
+ return this.request(withQuery('/api/products', params));
107
116
  }
108
117
  async getProduct(slug, domain) {
109
- const query = domain ? `?domain=${encodeURIComponent(domain)}` : '';
110
- return this.request(`/api/products/${slug}${query}`);
118
+ const params = new URLSearchParams();
119
+ appendDomains(params, domain);
120
+ return this.request(withQuery(`/api/products/${slug}`, params));
111
121
  }
112
- async getProductFile(slug, filepath) {
113
- return this.request(`/api/products/${slug}/files/${filepath}`);
122
+ async getProductFile(slug, filepath, options) {
123
+ const params = new URLSearchParams();
124
+ if (options?.format && options.format !== 'inline')
125
+ params.set('format', options.format);
126
+ if (options?.ttl !== undefined)
127
+ params.set('ttl', String(options.ttl));
128
+ return this.request(withQuery(`/api/products/${slug}/files/${filepath}`, params));
114
129
  }
115
130
  async updateProductFile(slug, filepath, body) {
116
131
  return this.request(`/api/products/${slug}/files/${filepath}`, {
@@ -147,8 +162,9 @@ export class ApiClient {
147
162
  });
148
163
  }
149
164
  async getProductIndex(slug, domain) {
150
- const query = domain ? `?domain=${encodeURIComponent(domain)}` : '';
151
- return this.request(`/api/products/${slug}/index${query}`);
165
+ const params = new URLSearchParams();
166
+ appendDomains(params, domain);
167
+ return this.request(withQuery(`/api/products/${slug}/index`, params));
152
168
  }
153
169
  async pullProductFiles(slug, options) {
154
170
  return this.request(`/api/products/${slug}/pull`, {
package/dist/index.js CHANGED
@@ -11,8 +11,15 @@ import { manageFoldersSchema, createManageFoldersTool, } from './tools/folders.j
11
11
  import { listProductsSchema, getProductSchema, readProductFileSchema, updateProductKnowledgeSchema, uploadProductFilesSchema, pullProductFilesSchema, createProductSchema, deleteProductSchema, deleteProductFileSchema, getProductHealthSchema, getProductIndexSchema, createListProductsTool, createGetProductTool, createReadProductFileTool, createUpdateProductKnowledgeTool, createUploadProductFilesTool, createPullProductFilesTool, createGetProductHealthTool, createGetProductIndexTool, createCreateProductTool, createDeleteProductTool, createDeleteProductFileTool, } from './tools/products.js';
12
12
  import { createUploadProductDirectoryTool, uploadProductDirectorySchema, UPLOAD_DIRECTORY_DESCRIPTION, } from './tools/upload-directory.js';
13
13
  import { addToBacklogSchema, listBacklogSchema, processBacklogSchema, createAddToBacklogTool, createListBacklogTool, createProcessBacklogTool, } from './tools/backlog.js';
14
+ import { DOMAIN_VALUES } from './tools/domains.js';
14
15
  const config = loadConfig();
15
16
  const client = new ApiClient(config);
17
+ const DOMAIN_FILTER_SCHEMA = {
18
+ oneOf: [
19
+ { type: 'string', enum: DOMAIN_VALUES },
20
+ { type: 'array', items: { type: 'string', enum: DOMAIN_VALUES } },
21
+ ],
22
+ };
16
23
  function prop(type, description, extra = {}) {
17
24
  return { type, description, ...extra };
18
25
  }
@@ -158,7 +165,7 @@ const toolDefinitions = [
158
165
  type: 'object',
159
166
  properties: {
160
167
  slug: prop('string', 'Product slug'),
161
- domain: prop('string', 'Filter files by domain', { enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'] }),
168
+ domain: { ...DOMAIN_FILTER_SCHEMA, description: 'Filter files by domain. Single domain or array — pass an array (e.g. ["brand", "advertising"]) to combine domains in one request.' },
162
169
  },
163
170
  required: ['slug'],
164
171
  },
@@ -167,12 +174,14 @@ const toolDefinitions = [
167
174
  },
168
175
  {
169
176
  name: 'read_product_file',
170
- description: 'Read a single file from a product by path. Use get_product to read all knowledge files at once (more efficient). Use read_product_file only when you need one specific file — works for markdown docs, JSON configs, and binary files (logos, PDFs returned as base64 data URIs).\n\nTypical workflow: get_product_index → find the file you need → read_product_file.',
177
+ description: 'Read a single file from a product by path. Use get_product to read all knowledge files at once (more efficient). Use read_product_file only when you need one specific file — works for markdown docs, JSON configs, and binary files.\n\nFor binary files (logos, screenshots, PDFs): pass `format: "signed-url"` to get a short-lived download URL instead of inline base64. Base64 inflates LLM context by ~33% and round-trips through your prompt; signed URLs let you `curl -o <path> <url>` directly to disk.\n\nTypical workflow: get_product_index → find the file you need → read_product_file (use signed-url for binaries).',
171
178
  inputSchema: {
172
179
  type: 'object',
173
180
  properties: {
174
181
  slug: prop('string', 'Product slug'),
175
182
  filepath: prop('string', 'File path within the product (e.g., "docs/research/user-research.md", "meta-ads.json", "brand-assets/logo.png")'),
183
+ format: { type: 'string', enum: ['inline', 'signed-url'], description: 'Response format. Default "inline" returns content directly. Use "signed-url" for binaries to avoid base64 context bloat — response will include a `url` field instead of `content`.' },
184
+ ttl: { type: 'number', description: 'Signed URL lifetime in seconds (default 600, max 3600). Ignored unless format="signed-url".' },
176
185
  },
177
186
  required: ['slug', 'filepath'],
178
187
  },
@@ -284,7 +293,7 @@ const toolDefinitions = [
284
293
  type: 'object',
285
294
  properties: {
286
295
  slug: prop('string', 'Product slug'),
287
- domain: prop('string', 'Filter index entries by domain', { enum: ['product-research', 'brand', 'copy', 'advertising', 'app-store'] }),
296
+ domain: { ...DOMAIN_FILTER_SCHEMA, description: 'Filter index entries by domain. Single domain or array.' },
288
297
  },
289
298
  required: ['slug'],
290
299
  },
@@ -9,23 +9,29 @@ export declare const listProductsSchema: z.ZodObject<{
9
9
  }>;
10
10
  export declare const getProductSchema: z.ZodObject<{
11
11
  slug: z.ZodString;
12
- domain: z.ZodOptional<z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>>;
12
+ domain: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>, z.ZodArray<z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>, "many">]>>;
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  slug: string;
15
- domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
15
+ domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | ("product-research" | "brand" | "copy" | "advertising" | "app-store")[] | undefined;
16
16
  }, {
17
17
  slug: string;
18
- domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
18
+ domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | ("product-research" | "brand" | "copy" | "advertising" | "app-store")[] | undefined;
19
19
  }>;
20
20
  export declare const readProductFileSchema: z.ZodObject<{
21
21
  slug: z.ZodString;
22
22
  filepath: z.ZodString;
23
+ format: z.ZodOptional<z.ZodEnum<["inline", "signed-url"]>>;
24
+ ttl: z.ZodOptional<z.ZodNumber>;
23
25
  }, "strip", z.ZodTypeAny, {
24
26
  slug: string;
25
27
  filepath: string;
28
+ format?: "inline" | "signed-url" | undefined;
29
+ ttl?: number | undefined;
26
30
  }, {
27
31
  slug: string;
28
32
  filepath: string;
33
+ format?: "inline" | "signed-url" | undefined;
34
+ ttl?: number | undefined;
29
35
  }>;
30
36
  export declare const updateProductKnowledgeSchema: z.ZodObject<{
31
37
  slug: z.ZodString;
@@ -91,13 +97,13 @@ export declare const pullProductFilesSchema: z.ZodObject<{
91
97
  }>;
92
98
  export declare const getProductIndexSchema: z.ZodObject<{
93
99
  slug: z.ZodString;
94
- domain: z.ZodOptional<z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>>;
100
+ domain: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>, z.ZodArray<z.ZodEnum<["product-research", "brand", "copy", "advertising", "app-store"]>, "many">]>>;
95
101
  }, "strip", z.ZodTypeAny, {
96
102
  slug: string;
97
- domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
103
+ domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | ("product-research" | "brand" | "copy" | "advertising" | "app-store")[] | undefined;
98
104
  }, {
99
105
  slug: string;
100
- domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | undefined;
106
+ domain?: "product-research" | "brand" | "copy" | "advertising" | "app-store" | ("product-research" | "brand" | "copy" | "advertising" | "app-store")[] | undefined;
101
107
  }>;
102
108
  export declare const getProductHealthSchema: z.ZodObject<{
103
109
  slug: z.ZodOptional<z.ZodString>;
@@ -174,6 +180,12 @@ export declare function createReadProductFileTool(client: ApiClient): (input: z.
174
180
  content: unknown;
175
181
  content_type: string;
176
182
  size?: number;
183
+ } | {
184
+ path: string;
185
+ url: string;
186
+ expires_at: string;
187
+ content_type: string;
188
+ size: number;
177
189
  }>;
178
190
  export declare function createUpdateProductKnowledgeTool(client: ApiClient): (input: z.infer<typeof updateProductKnowledgeSchema>) => Promise<{
179
191
  success: boolean;
@@ -9,11 +9,25 @@ export const listProductsSchema = z.object({
9
9
  });
10
10
  export const getProductSchema = z.object({
11
11
  slug: z.string().describe('Product slug'),
12
- domain: z.enum(DOMAIN_VALUES).optional().describe('Filter files by domain'),
12
+ domain: z
13
+ .union([z.enum(DOMAIN_VALUES), z.array(z.enum(DOMAIN_VALUES))])
14
+ .optional()
15
+ .describe('Filter files by domain. Pass a single domain ("brand") or an array (["brand", "advertising"]) to combine domains in one request — useful for creative pipelines that need brand assets plus advertising references without round-tripping operational config.'),
13
16
  });
14
17
  export const readProductFileSchema = z.object({
15
18
  slug: z.string().describe('Product slug'),
16
19
  filepath: z.string().describe('File path within the product (e.g., "docs/research/user-research.md", "meta-ads.json", "brand-assets/logo.png")'),
20
+ format: z
21
+ .enum(['inline', 'signed-url'])
22
+ .optional()
23
+ .describe('Response format. "inline" (default) returns the file content (text or base64 data URI for binary). "signed-url" returns a short-lived download URL — strongly preferred for binaries (logos, screenshots, PDFs) since base64 inflates LLM context by ~33%. Fetch with `curl -o <path> <url>`.'),
24
+ ttl: z
25
+ .number()
26
+ .int()
27
+ .positive()
28
+ .max(3600)
29
+ .optional()
30
+ .describe('Lifetime in seconds for the signed URL (default 600, max 3600). Ignored unless format="signed-url".'),
17
31
  });
18
32
  export const updateProductKnowledgeSchema = z.object({
19
33
  slug: z.string().describe('Product slug'),
@@ -43,7 +57,10 @@ export const pullProductFilesSchema = z.object({
43
57
  });
44
58
  export const getProductIndexSchema = z.object({
45
59
  slug: z.string().describe('Product slug'),
46
- domain: z.enum(DOMAIN_VALUES).optional().describe('Filter index entries by domain'),
60
+ domain: z
61
+ .union([z.enum(DOMAIN_VALUES), z.array(z.enum(DOMAIN_VALUES))])
62
+ .optional()
63
+ .describe('Filter index entries by domain. Pass a single domain or array of domains.'),
47
64
  });
48
65
  export const getProductHealthSchema = z.object({
49
66
  slug: z.string().optional().describe('Product slug. If omitted, returns health for all products.'),
@@ -75,7 +92,10 @@ export function createGetProductTool(client) {
75
92
  }
76
93
  export function createReadProductFileTool(client) {
77
94
  return async (input) => {
78
- const result = await client.getProductFile(input.slug, input.filepath);
95
+ const result = await client.getProductFile(input.slug, input.filepath, {
96
+ format: input.format,
97
+ ttl: input.ttl,
98
+ });
79
99
  return result;
80
100
  };
81
101
  }
@@ -9,16 +9,16 @@ export declare const uploadProductDirectorySchema: z.ZodObject<{
9
9
  exclude: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
10
  dry_run: z.ZodOptional<z.ZodBoolean>;
11
11
  }, "strip", z.ZodTypeAny, {
12
- slug: string;
13
12
  domain: "product-research" | "brand" | "copy" | "advertising" | "app-store";
13
+ slug: string;
14
14
  local_dir: string;
15
15
  dry_run?: boolean | undefined;
16
16
  dest_prefix?: string | undefined;
17
17
  glob?: string | undefined;
18
18
  exclude?: string[] | undefined;
19
19
  }, {
20
- slug: string;
21
20
  domain: "product-research" | "brand" | "copy" | "advertising" | "app-store";
21
+ slug: string;
22
22
  local_dir: string;
23
23
  dry_run?: boolean | undefined;
24
24
  dest_prefix?: string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "44reports-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for deploying and pulling context files (code, docs, data) to/from 44reports",
6
6
  "main": "dist/index.js",