@tigerdata/mcp-boilerplate 1.5.2 → 1.6.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { Cache } from './cache.js';
2
1
  export { cliEntrypoint } from './cliEntrypoint.js';
3
2
  export { httpServerFactory } from './httpServer.js';
4
3
  export { log } from './logger.js';
@@ -6,5 +5,6 @@ export type { AdditionalSetupArgs } from './mcpServer.js';
6
5
  export { registerExitHandlers } from './registerExitHandlers.js';
7
6
  export { StatusError } from './StatusError.js';
8
7
  export { stdioServerFactory } from './stdio.js';
8
+ export { ArrayStore, Store } from './store.js';
9
9
  export { addAiResultToSpan, withSpan } from './tracing.js';
10
10
  export type { ApiFactory, InferSchema, McpFeatureFlags, MigrationsConfig, ParsedQs, PromptFactory, ResourceFactory, } from './types.js';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- export { Cache } from './cache.js';
2
1
  export { cliEntrypoint } from './cliEntrypoint.js';
3
2
  export { httpServerFactory } from './httpServer.js';
4
3
  export { log } from './logger.js';
5
4
  export { registerExitHandlers } from './registerExitHandlers.js';
6
5
  export { StatusError } from './StatusError.js';
7
6
  export { stdioServerFactory } from './stdio.js';
7
+ export { ArrayStore, Store } from './store.js';
8
8
  export { addAiResultToSpan, withSpan } from './tracing.js';
@@ -168,6 +168,8 @@ const doLoadSkills = async (octokit) => {
168
168
  for (const entry of dirEntries) {
169
169
  if (entry.isFile())
170
170
  continue;
171
+ if (entry.name.startsWith('.'))
172
+ continue;
171
173
  if (!entry.isDirectory()) {
172
174
  log.warn(`Skipping non-directory entry in local_collection`, {
173
175
  path: `${cfg.path}/${entry.name}`,
@@ -214,6 +216,8 @@ const doLoadSkills = async (octokit) => {
214
216
  for (const entry of dirResponse.data) {
215
217
  if (entry.type === 'file')
216
218
  continue;
219
+ if (entry.name.startsWith('.'))
220
+ continue;
217
221
  if (entry.type !== 'dir') {
218
222
  log.warn(`Skipping non-directory entry in github_collection`, {
219
223
  path: `${cfg.repo}/${entry.path}`,
@@ -6,7 +6,7 @@ describe('Skills API', () => {
6
6
  describe('getAvailableSkillNames', () => {
7
7
  it('returns a comma-separated list of visible skill names in alphabetical order when skills are loaded', async () => {
8
8
  const names = await getAvailableSkillNames({});
9
- expect(names).toBe('first-skill, second-skill');
9
+ expect(names).toBe('collection-a, collection-b, first-skill, second-skill');
10
10
  });
11
11
  it('returns "(none)" when flags filter out every skill (e.g. enabledSkills does not match any)', async () => {
12
12
  const names = await getAvailableSkillNames({
@@ -36,11 +36,13 @@ describe('Skills API', () => {
36
36
  expect(result).toContain('<available_skills>');
37
37
  expect(result).toContain('first-skill');
38
38
  expect(result).toContain('second-skill');
39
+ expect(result).toContain('collection-a');
40
+ expect(result).toContain('collection-b');
39
41
  const inner = result
40
42
  .split('<available_skills>')[1]
41
43
  .split('</available_skills>')[0];
42
44
  const lines = inner.trim().split('\n');
43
- expect(lines).toHaveLength(3);
45
+ expect(lines).toHaveLength(5);
44
46
  });
45
47
  it('read valid skill: returns the content of the skill SKILL.md when name and path are valid', async () => {
46
48
  const result = await viewSkillContent({
@@ -107,4 +109,23 @@ describe('Skills API', () => {
107
109
  })).rejects.toThrow(InvalidPathError);
108
110
  });
109
111
  });
112
+ describe('local_collection', () => {
113
+ it('loads every real skill directory inside the collection', async () => {
114
+ const names = await getAvailableSkillNames({});
115
+ expect(names).toContain('collection-a');
116
+ expect(names).toContain('collection-b');
117
+ });
118
+ it('exposes collection skill content via viewSkillContent', async () => {
119
+ const result = await viewSkillContent({
120
+ name: 'collection-a',
121
+ path: 'SKILL.md',
122
+ });
123
+ expect(result).toBe('Collection skill A content\n');
124
+ });
125
+ it('skips dot-prefixed directories (e.g. .github) even when they contain a valid SKILL.md', async () => {
126
+ const names = await getAvailableSkillNames({});
127
+ expect(names).not.toContain('dot-github-trap');
128
+ await expect(viewSkillContent({ name: 'dot-github-trap', path: 'SKILL.md' })).rejects.toBeInstanceOf(SkillNotFoundError);
129
+ });
130
+ });
110
131
  });
@@ -0,0 +1,22 @@
1
+ interface StoreProps<T> {
2
+ fetch: () => Promise<T>;
3
+ ttl?: number;
4
+ }
5
+ export declare class Store<T> {
6
+ private contents;
7
+ private fetch;
8
+ private ttl?;
9
+ private expirationDateTime?;
10
+ constructor({ fetch, ttl }: StoreProps<T>);
11
+ get(): Promise<T>;
12
+ }
13
+ interface ArrayStoreProps<T> {
14
+ fetch: () => Promise<T[]>;
15
+ ttl?: number;
16
+ }
17
+ export declare class ArrayStore<T> extends Store<T[]> {
18
+ constructor({ fetch, ttl }: ArrayStoreProps<T>);
19
+ find(predicate: (item: T) => boolean): Promise<T | null>;
20
+ filter(predicate: (item: T) => boolean): Promise<T[]>;
21
+ }
22
+ export {};
@@ -1,20 +1,30 @@
1
- export class Cache {
1
+ export class Store {
2
2
  contents = null;
3
3
  fetch;
4
+ ttl;
4
5
  expirationDateTime;
5
6
  constructor({ fetch, ttl }) {
6
7
  this.fetch = fetch;
7
8
  if (ttl) {
9
+ this.ttl = ttl;
8
10
  this.expirationDateTime = Date.now() + ttl;
9
11
  }
10
12
  }
11
13
  async get() {
12
14
  if (this.expirationDateTime && Date.now() > this.expirationDateTime) {
13
15
  this.contents = null;
16
+ if (this.ttl) {
17
+ this.expirationDateTime = Date.now() + this.ttl;
18
+ }
14
19
  }
15
20
  this.contents ??= this.fetch();
16
21
  return this.contents;
17
22
  }
23
+ }
24
+ export class ArrayStore extends Store {
25
+ constructor({ fetch, ttl }) {
26
+ super({ fetch, ttl });
27
+ }
18
28
  async find(predicate) {
19
29
  const items = await this.get();
20
30
  return items.find(predicate) ?? null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tigerdata/mcp-boilerplate",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "MCP boilerplate code for Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "author": "TigerData",
package/dist/cache.d.ts DELETED
@@ -1,15 +0,0 @@
1
- interface CacheProps<T> {
2
- contents?: T;
3
- fetch: () => Promise<T[]>;
4
- ttl?: number;
5
- }
6
- export declare class Cache<T> {
7
- private contents;
8
- private fetch;
9
- private expirationDateTime?;
10
- constructor({ fetch, ttl }: CacheProps<T>);
11
- get(): Promise<T[]>;
12
- find(predicate: (item: T) => boolean): Promise<T | null>;
13
- filter(predicate: (item: T) => boolean): Promise<T[]>;
14
- }
15
- export {};