@tigerdata/mcp-boilerplate 0.9.2 → 1.0.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/README.md +7 -10
- package/dist/http/api.d.ts +1 -1
- package/dist/http/api.js +2 -2
- package/dist/http/mcp.d.ts +2 -2
- package/dist/http/mcp.js +3 -3
- package/dist/httpServer.d.ts +2 -2
- package/dist/httpServer.js +5 -3
- package/dist/mcpServer.d.ts +2 -2
- package/dist/mcpServer.js +4 -4
- package/dist/skills/index.d.ts +5 -0
- package/dist/skills/index.js +5 -0
- package/dist/skills/prompts.d.ts +8 -0
- package/dist/skills/prompts.js +31 -0
- package/dist/skills/resource.d.ts +12 -0
- package/dist/skills/resource.js +47 -0
- package/dist/skills/tool.d.ts +13 -0
- package/dist/skills/tool.js +37 -0
- package/dist/skills/types.d.ts +363 -0
- package/dist/skills/types.js +58 -0
- package/dist/skills/utils.d.ts +33 -0
- package/dist/skills/utils.js +404 -0
- package/dist/stdio.js +1 -1
- package/dist/types.d.ts +4 -4
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -2,20 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This provides some common code for creating a [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) server in Node.js.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Usage
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @tigerdata/mcp-boilerplate
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
git clone <repository-url>
|
|
11
|
-
cd mcp-boilerplate-node
|
|
12
|
-
```
|
|
11
|
+
See [tiger-skills-mcp-server](https://github.com/tigerdata/tiger-skills-mcp-server) for an example MCP server using this boilerplate.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
### Skills
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
./bun install
|
|
18
|
-
```
|
|
15
|
+
Add skills support to your MCP server by leveraging the skills submodule in `@tigerdata/mcp-boilerplate/skills`. See [src/skills/README.md](./skills/README.md) for details.
|
|
19
16
|
|
|
20
17
|
## Eslint Plugin
|
|
21
18
|
|
package/dist/http/api.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* REST API alternative to MCP for direct use of the same tools.
|
|
3
3
|
*/
|
|
4
4
|
import type { BaseApiFactory, RouterFactoryResult } from '../types.js';
|
|
5
|
-
export declare const apiRouterFactory: <Context extends Record<string, unknown>>(context: Context, apiFactories: readonly BaseApiFactory<Context>[]) => RouterFactoryResult
|
|
5
|
+
export declare const apiRouterFactory: <Context extends Record<string, unknown>>(context: Context, apiFactories: readonly BaseApiFactory<Context>[]) => Promise<RouterFactoryResult>;
|
package/dist/http/api.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
import bodyParser from 'body-parser';
|
|
5
5
|
import { Router } from 'express';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
export const apiRouterFactory = (context, apiFactories) => {
|
|
7
|
+
export const apiRouterFactory = async (context, apiFactories) => {
|
|
8
8
|
const router = Router();
|
|
9
9
|
router.use(bodyParser.json());
|
|
10
10
|
for (const factory of apiFactories) {
|
|
11
|
-
const tool = factory(context, {});
|
|
11
|
+
const tool = await factory(context, {});
|
|
12
12
|
if (!tool.method || !tool.route)
|
|
13
13
|
continue;
|
|
14
14
|
router[tool.method](tool.route, async (req, res) => {
|
package/dist/http/mcp.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { McpFeatureFlags, RouterFactoryResult } from '../types.js';
|
|
3
|
-
export declare const mcpRouterFactory: <Context extends Record<string, unknown>>(context: Context, createServer: (context: Context, featureFlags: McpFeatureFlags) => {
|
|
3
|
+
export declare const mcpRouterFactory: <Context extends Record<string, unknown>>(context: Context, createServer: (context: Context, featureFlags: McpFeatureFlags) => Promise<{
|
|
4
4
|
server: McpServer;
|
|
5
|
-
}
|
|
5
|
+
}>, { name, stateful, inspector, }?: {
|
|
6
6
|
name?: string;
|
|
7
7
|
stateful?: boolean;
|
|
8
8
|
inspector?: boolean;
|
package/dist/http/mcp.js
CHANGED
|
@@ -33,7 +33,7 @@ export const mcpRouterFactory = (context, createServer, { name, stateful = true,
|
|
|
33
33
|
});
|
|
34
34
|
const handleStatelessRequest = async (req, res) => {
|
|
35
35
|
const featureFlags = parseFeatureFlags(req);
|
|
36
|
-
const { server } = createServer(context, featureFlags);
|
|
36
|
+
const { server } = await createServer(context, featureFlags);
|
|
37
37
|
const transport = new StreamableHTTPServerTransport({
|
|
38
38
|
sessionIdGenerator: undefined,
|
|
39
39
|
});
|
|
@@ -98,7 +98,7 @@ export const mcpRouterFactory = (context, createServer, { name, stateful = true,
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
});
|
|
101
|
-
const { server } = createServer(context, featureFlags);
|
|
101
|
+
const { server } = await createServer(context, featureFlags);
|
|
102
102
|
await server.connect(transport);
|
|
103
103
|
}
|
|
104
104
|
await transport.handleRequest(req, res, body);
|
|
@@ -201,7 +201,7 @@ export const mcpRouterFactory = (context, createServer, { name, stateful = true,
|
|
|
201
201
|
${inspector
|
|
202
202
|
? `
|
|
203
203
|
<h3>Inspector</h3>
|
|
204
|
-
<p>You can use the <a href="/inspector">MCP Inspector</a> for testing purposes.</p>`
|
|
204
|
+
<p>You can use the <a href="/inspector?server=${encodeURIComponent(fullUrl)}">MCP Inspector</a> for testing purposes.</p>`
|
|
205
205
|
: ''}
|
|
206
206
|
<h3>Claude Code</h3>
|
|
207
207
|
<p>To connect to this MCP server using Claude Code, run the following command in your terminal:</p>
|
package/dist/httpServer.d.ts
CHANGED
|
@@ -14,10 +14,10 @@ export declare const httpServerFactory: <Context extends Record<string, unknown>
|
|
|
14
14
|
cleanupFn?: () => void | Promise<void>;
|
|
15
15
|
stateful?: boolean;
|
|
16
16
|
instructions?: string;
|
|
17
|
-
}) => {
|
|
17
|
+
}) => Promise<{
|
|
18
18
|
app: express.Express;
|
|
19
19
|
server: Server;
|
|
20
20
|
apiRouter: express.Router;
|
|
21
21
|
mcpRouter: express.Router;
|
|
22
22
|
registerCleanupFn: (fn: () => Promise<void>) => void;
|
|
23
|
-
}
|
|
23
|
+
}>;
|
package/dist/httpServer.js
CHANGED
|
@@ -7,7 +7,7 @@ import { log } from './logger.js';
|
|
|
7
7
|
import { mcpServerFactory } from './mcpServer.js';
|
|
8
8
|
import { registerExitHandlers } from './registerExitHandlers.js';
|
|
9
9
|
import { StatusError } from './StatusError.js';
|
|
10
|
-
export const httpServerFactory = ({ name, version, context, apiFactories = [], promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful = true, instructions, }) => {
|
|
10
|
+
export const httpServerFactory = async ({ name, version, context, apiFactories = [], promptFactories, resourceFactories, additionalSetup, cleanupFn, stateful = true, instructions, }) => {
|
|
11
11
|
const cleanupFns = cleanupFn
|
|
12
12
|
? [cleanupFn]
|
|
13
13
|
: [];
|
|
@@ -31,7 +31,7 @@ export const httpServerFactory = ({ name, version, context, apiFactories = [], p
|
|
|
31
31
|
}), { name, stateful, inspector });
|
|
32
32
|
cleanupFns.push(mcpCleanup);
|
|
33
33
|
app.use('/mcp', mcpRouter);
|
|
34
|
-
const [apiRouter, apiCleanup] = apiRouterFactory(context, apiFactories);
|
|
34
|
+
const [apiRouter, apiCleanup] = await apiRouterFactory(context, apiFactories);
|
|
35
35
|
cleanupFns.push(apiCleanup);
|
|
36
36
|
app.use('/api', apiRouter);
|
|
37
37
|
// Error handler
|
|
@@ -54,7 +54,9 @@ export const httpServerFactory = ({ name, version, context, apiFactories = [], p
|
|
|
54
54
|
import('@mcp-use/inspector')
|
|
55
55
|
.then(({ mountInspector }) => {
|
|
56
56
|
app.use(bodyParser.json());
|
|
57
|
-
mountInspector(app, {
|
|
57
|
+
mountInspector(app, {
|
|
58
|
+
autoConnectUrl: process.env.MCP_PUBLIC_URL ?? `http://localhost:${PORT}/mcp`,
|
|
59
|
+
});
|
|
58
60
|
})
|
|
59
61
|
.catch(log.error);
|
|
60
62
|
}
|
package/dist/mcpServer.d.ts
CHANGED
package/dist/mcpServer.js
CHANGED
|
@@ -25,7 +25,7 @@ const shouldSkip = (item, enabledSets, disabledSets) => {
|
|
|
25
25
|
}
|
|
26
26
|
return false;
|
|
27
27
|
};
|
|
28
|
-
export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactories = [], promptFactories = [], resourceFactories = [], additionalSetup, additionalCapabilities = {}, featureFlags = {}, instructions, }) => {
|
|
28
|
+
export const mcpServerFactory = async ({ name, version = '1.0.0', context, apiFactories = [], promptFactories = [], resourceFactories = [], additionalSetup, additionalCapabilities = {}, featureFlags = {}, instructions, }) => {
|
|
29
29
|
const enablePrompts = featureFlags.prompts !== false;
|
|
30
30
|
const enableResources = featureFlags.resources !== false;
|
|
31
31
|
const enableTools = featureFlags.tools !== false;
|
|
@@ -45,7 +45,7 @@ export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactorie
|
|
|
45
45
|
});
|
|
46
46
|
if (enableTools) {
|
|
47
47
|
for (const factory of apiFactories) {
|
|
48
|
-
const tool = factory(context, featureFlags);
|
|
48
|
+
const tool = await factory(context, featureFlags);
|
|
49
49
|
if (shouldSkip(tool, [enabledTools, featureFlags.enabledTools], [disabledTools, featureFlags.disabledTools])) {
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
@@ -111,7 +111,7 @@ export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactorie
|
|
|
111
111
|
}
|
|
112
112
|
if (enablePrompts) {
|
|
113
113
|
for (const factory of promptFactories) {
|
|
114
|
-
const prompt = factory(context, featureFlags);
|
|
114
|
+
const prompt = await factory(context, featureFlags);
|
|
115
115
|
if (shouldSkip(prompt, [enabledPrompts, featureFlags.enabledPrompts], [disabledPrompts, featureFlags.disabledPrompts])) {
|
|
116
116
|
continue;
|
|
117
117
|
}
|
|
@@ -139,7 +139,7 @@ export const mcpServerFactory = ({ name, version = '1.0.0', context, apiFactorie
|
|
|
139
139
|
}
|
|
140
140
|
if (enableResources) {
|
|
141
141
|
for (const factory of resourceFactories) {
|
|
142
|
-
const resource = factory(context, featureFlags);
|
|
142
|
+
const resource = await factory(context, featureFlags);
|
|
143
143
|
if (shouldSkip(resource, [enabledResources, featureFlags.enabledResources], [disabledResources, featureFlags.disabledResources])) {
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Octokit } from '@octokit/rest';
|
|
2
|
+
import type { PromptFactory } from '../types.js';
|
|
3
|
+
import type { ServerContextWithOctokit } from './types.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
octokit?: Octokit;
|
|
6
|
+
}
|
|
7
|
+
export declare const createSkillsPromptFactories: (options?: Options) => Promise<PromptFactory<ServerContextWithOctokit, Record<string, never>>[]>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { loadSkills, parseSkillsFlags, viewSkillContent } from './utils.js';
|
|
2
|
+
// Create a prompt for each skill from the main SKILL.md files.
|
|
3
|
+
export const createSkillsPromptFactories = async (options = {}) => {
|
|
4
|
+
const skills = await loadSkills(options);
|
|
5
|
+
return Array.from(skills.entries()).map(([name, skillData]) => ({ octokit }, { query }) => ({
|
|
6
|
+
name,
|
|
7
|
+
config: {
|
|
8
|
+
// Using the dash-separated name as the title to work around a problem in Claude Code
|
|
9
|
+
// See https://github.com/anthropics/claude-code/issues/7464
|
|
10
|
+
title: name,
|
|
11
|
+
description: skillData.description,
|
|
12
|
+
inputSchema: {}, // No arguments for static skills
|
|
13
|
+
},
|
|
14
|
+
fn: async () => {
|
|
15
|
+
const flags = parseSkillsFlags(query);
|
|
16
|
+
const content = await viewSkillContent({ octokit, flags, name });
|
|
17
|
+
return {
|
|
18
|
+
description: skillData.description || name,
|
|
19
|
+
messages: [
|
|
20
|
+
{
|
|
21
|
+
role: 'user',
|
|
22
|
+
content: {
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: content,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpFeatureFlags, ResourceFactory } from '../types.js';
|
|
2
|
+
import type { ServerContextWithOctokit } from './types.js';
|
|
3
|
+
interface Options<Context extends ServerContextWithOctokit> {
|
|
4
|
+
appendSkillsListToDescription?: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean | ((ctx: Context, flags: McpFeatureFlags) => boolean | Promise<boolean>);
|
|
7
|
+
name?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
uriScheme?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const createSkillsResourceFactory: <Context extends ServerContextWithOctokit>(options?: Options<Context>) => ResourceFactory<ServerContextWithOctokit>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { listSkills, loadSkills, parseSkillsFlags, skillsDescription, skillVisible, viewSkillContent, } from './utils.js';
|
|
2
|
+
export const createSkillsResourceFactory = (options = {}) => async (ctx, mcpFlags) => {
|
|
3
|
+
const { octokit } = ctx;
|
|
4
|
+
const { query } = mcpFlags;
|
|
5
|
+
const flags = parseSkillsFlags(query);
|
|
6
|
+
return {
|
|
7
|
+
type: 'templated',
|
|
8
|
+
name: options.name || 'skills',
|
|
9
|
+
disabled: typeof options.disabled === 'function'
|
|
10
|
+
? await options.disabled(ctx, mcpFlags)
|
|
11
|
+
: options.disabled,
|
|
12
|
+
config: {
|
|
13
|
+
title: options.title || 'Skills',
|
|
14
|
+
description: `${options.description || skillsDescription}${options.appendSkillsListToDescription
|
|
15
|
+
? `\n\n## Available Skills\n\n${await listSkills({ octokit, flags: parseSkillsFlags(query) })}`
|
|
16
|
+
: ''}`,
|
|
17
|
+
},
|
|
18
|
+
uriTemplate: `${options.uriScheme || 'skills'}://{name}{?path}`,
|
|
19
|
+
list: async () => {
|
|
20
|
+
const skills = await loadSkills({ octokit });
|
|
21
|
+
return {
|
|
22
|
+
resources: Array.from(skills.values())
|
|
23
|
+
.filter((s) => skillVisible(s.name, flags))
|
|
24
|
+
.map((skill) => ({
|
|
25
|
+
uri: `${options.uriScheme || 'skills'}://${skill.name}?path=SKILL.md`,
|
|
26
|
+
name: skill.name,
|
|
27
|
+
title: skill.name,
|
|
28
|
+
description: skill.description,
|
|
29
|
+
mimeType: 'text/markdown',
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
read: async (uri, { name, path }) => {
|
|
34
|
+
if (Array.isArray(name) || Array.isArray(path) || !name) {
|
|
35
|
+
throw new Error('Invalid parameters');
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
contents: [
|
|
39
|
+
{
|
|
40
|
+
uri: uri.href,
|
|
41
|
+
text: await viewSkillContent({ octokit, flags, name, path }),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ApiFactory, McpFeatureFlags } from '../types.js';
|
|
2
|
+
import { type ServerContextWithOctokit, zViewSkillInputSchema, zViewSkillOutputSchema } from './types.js';
|
|
3
|
+
interface Options<Context extends ServerContextWithOctokit> {
|
|
4
|
+
appendSkillsListToDescription?: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean | ((ctx: Context, flags: McpFeatureFlags) => boolean | Promise<boolean>);
|
|
7
|
+
method?: 'get' | 'post';
|
|
8
|
+
name?: string;
|
|
9
|
+
route?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const createViewSkillToolFactory: <Context extends ServerContextWithOctokit>(options?: Options<Context>) => ApiFactory<ServerContextWithOctokit, typeof zViewSkillInputSchema, typeof zViewSkillOutputSchema>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { zViewSkillInputSchema, zViewSkillOutputSchema, } from './types.js';
|
|
2
|
+
import { listSkills, parseSkillsFlags, skillsDescription, viewSkillContent, } from './utils.js';
|
|
3
|
+
export const createViewSkillToolFactory = (options = {}) => async (ctx, mcpFlags) => {
|
|
4
|
+
const { octokit } = ctx;
|
|
5
|
+
const flags = parseSkillsFlags(mcpFlags.query);
|
|
6
|
+
return {
|
|
7
|
+
name: options.name || 'view',
|
|
8
|
+
disabled: typeof options.disabled === 'function'
|
|
9
|
+
? await options.disabled(ctx, mcpFlags)
|
|
10
|
+
: options.disabled,
|
|
11
|
+
method: options.method || 'get',
|
|
12
|
+
route: options.route || '/view',
|
|
13
|
+
config: {
|
|
14
|
+
title: options.title || 'View Skill',
|
|
15
|
+
description: `${options.description || skillsDescription}${options.appendSkillsListToDescription
|
|
16
|
+
? `\n\n## Available Skills\n\n${await listSkills({ octokit, flags })}`
|
|
17
|
+
: ''}`,
|
|
18
|
+
inputSchema: zViewSkillInputSchema,
|
|
19
|
+
outputSchema: zViewSkillOutputSchema,
|
|
20
|
+
},
|
|
21
|
+
fn: async ({ skill_name: name, path, }) => {
|
|
22
|
+
if (!name || name === '.') {
|
|
23
|
+
return {
|
|
24
|
+
content: await listSkills({ octokit, flags }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
content: await viewSkillContent({
|
|
29
|
+
octokit,
|
|
30
|
+
flags,
|
|
31
|
+
name,
|
|
32
|
+
path,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type { Octokit } from '@octokit/rest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { InferSchema } from '../types.js';
|
|
4
|
+
export declare const zSkillType: z.ZodEnum<["local", "local_collection", "github", "github_collection"]>;
|
|
5
|
+
export type SkillType = z.infer<typeof zSkillType>;
|
|
6
|
+
export declare const zLocalSkillCfg: z.ZodObject<{
|
|
7
|
+
type: z.ZodLiteral<"local">;
|
|
8
|
+
path: z.ZodString;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
type: "local";
|
|
11
|
+
path: string;
|
|
12
|
+
}, {
|
|
13
|
+
type: "local";
|
|
14
|
+
path: string;
|
|
15
|
+
}>;
|
|
16
|
+
export type LocalSkillCfg = z.infer<typeof zLocalSkillCfg>;
|
|
17
|
+
declare const zCollectionFlagsCfg: z.ZodObject<{
|
|
18
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
enabled_skills?: string[] | undefined;
|
|
23
|
+
disabled_skills?: string[] | undefined;
|
|
24
|
+
ignored_paths?: string[] | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
enabled_skills?: string[] | undefined;
|
|
27
|
+
disabled_skills?: string[] | undefined;
|
|
28
|
+
ignored_paths?: string[] | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
export type CollectionFlagsCfg = z.infer<typeof zCollectionFlagsCfg>;
|
|
31
|
+
export interface CollectionFlags {
|
|
32
|
+
enabledSkills: Set<string> | null;
|
|
33
|
+
disabledSkills: Set<string> | null;
|
|
34
|
+
ignoredPaths: Set<string> | null;
|
|
35
|
+
}
|
|
36
|
+
export declare const zLocalCollectionSkillCfg: z.ZodObject<{
|
|
37
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
38
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
39
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
40
|
+
} & {
|
|
41
|
+
type: z.ZodLiteral<"local_collection">;
|
|
42
|
+
path: z.ZodString;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
type: "local_collection";
|
|
45
|
+
path: string;
|
|
46
|
+
enabled_skills?: string[] | undefined;
|
|
47
|
+
disabled_skills?: string[] | undefined;
|
|
48
|
+
ignored_paths?: string[] | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
type: "local_collection";
|
|
51
|
+
path: string;
|
|
52
|
+
enabled_skills?: string[] | undefined;
|
|
53
|
+
disabled_skills?: string[] | undefined;
|
|
54
|
+
ignored_paths?: string[] | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
export type LocalCollectionSkillCfg = z.infer<typeof zLocalCollectionSkillCfg>;
|
|
57
|
+
export declare const zGitHubSkillCfg: z.ZodObject<{
|
|
58
|
+
type: z.ZodLiteral<"github">;
|
|
59
|
+
repo: z.ZodString;
|
|
60
|
+
path: z.ZodOptional<z.ZodString>;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
type: "github";
|
|
63
|
+
repo: string;
|
|
64
|
+
path?: string | undefined;
|
|
65
|
+
}, {
|
|
66
|
+
type: "github";
|
|
67
|
+
repo: string;
|
|
68
|
+
path?: string | undefined;
|
|
69
|
+
}>;
|
|
70
|
+
export type GitHubSkillCfg = z.infer<typeof zGitHubSkillCfg>;
|
|
71
|
+
export declare const zGitHubCollectionSkillCfg: z.ZodObject<{
|
|
72
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
73
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
74
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
75
|
+
} & {
|
|
76
|
+
type: z.ZodLiteral<"github_collection">;
|
|
77
|
+
repo: z.ZodString;
|
|
78
|
+
path: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
type: "github_collection";
|
|
81
|
+
repo: string;
|
|
82
|
+
path?: string | undefined;
|
|
83
|
+
enabled_skills?: string[] | undefined;
|
|
84
|
+
disabled_skills?: string[] | undefined;
|
|
85
|
+
ignored_paths?: string[] | undefined;
|
|
86
|
+
}, {
|
|
87
|
+
type: "github_collection";
|
|
88
|
+
repo: string;
|
|
89
|
+
path?: string | undefined;
|
|
90
|
+
enabled_skills?: string[] | undefined;
|
|
91
|
+
disabled_skills?: string[] | undefined;
|
|
92
|
+
ignored_paths?: string[] | undefined;
|
|
93
|
+
}>;
|
|
94
|
+
export type GitHubCollectionSkillCfg = z.infer<typeof zGitHubCollectionSkillCfg>;
|
|
95
|
+
export declare const zSkillCfg: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
96
|
+
type: z.ZodLiteral<"local">;
|
|
97
|
+
path: z.ZodString;
|
|
98
|
+
}, "strip", z.ZodTypeAny, {
|
|
99
|
+
type: "local";
|
|
100
|
+
path: string;
|
|
101
|
+
}, {
|
|
102
|
+
type: "local";
|
|
103
|
+
path: string;
|
|
104
|
+
}>, z.ZodObject<{
|
|
105
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
106
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
107
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
108
|
+
} & {
|
|
109
|
+
type: z.ZodLiteral<"local_collection">;
|
|
110
|
+
path: z.ZodString;
|
|
111
|
+
}, "strip", z.ZodTypeAny, {
|
|
112
|
+
type: "local_collection";
|
|
113
|
+
path: string;
|
|
114
|
+
enabled_skills?: string[] | undefined;
|
|
115
|
+
disabled_skills?: string[] | undefined;
|
|
116
|
+
ignored_paths?: string[] | undefined;
|
|
117
|
+
}, {
|
|
118
|
+
type: "local_collection";
|
|
119
|
+
path: string;
|
|
120
|
+
enabled_skills?: string[] | undefined;
|
|
121
|
+
disabled_skills?: string[] | undefined;
|
|
122
|
+
ignored_paths?: string[] | undefined;
|
|
123
|
+
}>, z.ZodObject<{
|
|
124
|
+
type: z.ZodLiteral<"github">;
|
|
125
|
+
repo: z.ZodString;
|
|
126
|
+
path: z.ZodOptional<z.ZodString>;
|
|
127
|
+
}, "strip", z.ZodTypeAny, {
|
|
128
|
+
type: "github";
|
|
129
|
+
repo: string;
|
|
130
|
+
path?: string | undefined;
|
|
131
|
+
}, {
|
|
132
|
+
type: "github";
|
|
133
|
+
repo: string;
|
|
134
|
+
path?: string | undefined;
|
|
135
|
+
}>, z.ZodObject<{
|
|
136
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
137
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
138
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
139
|
+
} & {
|
|
140
|
+
type: z.ZodLiteral<"github_collection">;
|
|
141
|
+
repo: z.ZodString;
|
|
142
|
+
path: z.ZodOptional<z.ZodString>;
|
|
143
|
+
}, "strip", z.ZodTypeAny, {
|
|
144
|
+
type: "github_collection";
|
|
145
|
+
repo: string;
|
|
146
|
+
path?: string | undefined;
|
|
147
|
+
enabled_skills?: string[] | undefined;
|
|
148
|
+
disabled_skills?: string[] | undefined;
|
|
149
|
+
ignored_paths?: string[] | undefined;
|
|
150
|
+
}, {
|
|
151
|
+
type: "github_collection";
|
|
152
|
+
repo: string;
|
|
153
|
+
path?: string | undefined;
|
|
154
|
+
enabled_skills?: string[] | undefined;
|
|
155
|
+
disabled_skills?: string[] | undefined;
|
|
156
|
+
ignored_paths?: string[] | undefined;
|
|
157
|
+
}>]>;
|
|
158
|
+
export type SkillCfg = z.infer<typeof zSkillCfg>;
|
|
159
|
+
export declare const zSkillCfgMap: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
160
|
+
type: z.ZodLiteral<"local">;
|
|
161
|
+
path: z.ZodString;
|
|
162
|
+
}, "strip", z.ZodTypeAny, {
|
|
163
|
+
type: "local";
|
|
164
|
+
path: string;
|
|
165
|
+
}, {
|
|
166
|
+
type: "local";
|
|
167
|
+
path: string;
|
|
168
|
+
}>, z.ZodObject<{
|
|
169
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
170
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
171
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
172
|
+
} & {
|
|
173
|
+
type: z.ZodLiteral<"local_collection">;
|
|
174
|
+
path: z.ZodString;
|
|
175
|
+
}, "strip", z.ZodTypeAny, {
|
|
176
|
+
type: "local_collection";
|
|
177
|
+
path: string;
|
|
178
|
+
enabled_skills?: string[] | undefined;
|
|
179
|
+
disabled_skills?: string[] | undefined;
|
|
180
|
+
ignored_paths?: string[] | undefined;
|
|
181
|
+
}, {
|
|
182
|
+
type: "local_collection";
|
|
183
|
+
path: string;
|
|
184
|
+
enabled_skills?: string[] | undefined;
|
|
185
|
+
disabled_skills?: string[] | undefined;
|
|
186
|
+
ignored_paths?: string[] | undefined;
|
|
187
|
+
}>, z.ZodObject<{
|
|
188
|
+
type: z.ZodLiteral<"github">;
|
|
189
|
+
repo: z.ZodString;
|
|
190
|
+
path: z.ZodOptional<z.ZodString>;
|
|
191
|
+
}, "strip", z.ZodTypeAny, {
|
|
192
|
+
type: "github";
|
|
193
|
+
repo: string;
|
|
194
|
+
path?: string | undefined;
|
|
195
|
+
}, {
|
|
196
|
+
type: "github";
|
|
197
|
+
repo: string;
|
|
198
|
+
path?: string | undefined;
|
|
199
|
+
}>, z.ZodObject<{
|
|
200
|
+
enabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
201
|
+
disabled_skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
202
|
+
ignored_paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
203
|
+
} & {
|
|
204
|
+
type: z.ZodLiteral<"github_collection">;
|
|
205
|
+
repo: z.ZodString;
|
|
206
|
+
path: z.ZodOptional<z.ZodString>;
|
|
207
|
+
}, "strip", z.ZodTypeAny, {
|
|
208
|
+
type: "github_collection";
|
|
209
|
+
repo: string;
|
|
210
|
+
path?: string | undefined;
|
|
211
|
+
enabled_skills?: string[] | undefined;
|
|
212
|
+
disabled_skills?: string[] | undefined;
|
|
213
|
+
ignored_paths?: string[] | undefined;
|
|
214
|
+
}, {
|
|
215
|
+
type: "github_collection";
|
|
216
|
+
repo: string;
|
|
217
|
+
path?: string | undefined;
|
|
218
|
+
enabled_skills?: string[] | undefined;
|
|
219
|
+
disabled_skills?: string[] | undefined;
|
|
220
|
+
ignored_paths?: string[] | undefined;
|
|
221
|
+
}>]>>;
|
|
222
|
+
export type SkillCfgMap = z.infer<typeof zSkillCfgMap>;
|
|
223
|
+
export declare const zSkillMatter: z.ZodObject<{
|
|
224
|
+
name: z.ZodString;
|
|
225
|
+
description: z.ZodString;
|
|
226
|
+
}, "strip", z.ZodTypeAny, {
|
|
227
|
+
description: string;
|
|
228
|
+
name: string;
|
|
229
|
+
}, {
|
|
230
|
+
description: string;
|
|
231
|
+
name: string;
|
|
232
|
+
}>;
|
|
233
|
+
export type SkillMatter = z.infer<typeof zSkillMatter>;
|
|
234
|
+
export declare const zLocalSkill: z.ZodObject<{
|
|
235
|
+
name: z.ZodString;
|
|
236
|
+
description: z.ZodString;
|
|
237
|
+
} & {
|
|
238
|
+
type: z.ZodLiteral<"local">;
|
|
239
|
+
path: z.ZodString;
|
|
240
|
+
}, "strip", z.ZodTypeAny, {
|
|
241
|
+
type: "local";
|
|
242
|
+
description: string;
|
|
243
|
+
name: string;
|
|
244
|
+
path: string;
|
|
245
|
+
}, {
|
|
246
|
+
type: "local";
|
|
247
|
+
description: string;
|
|
248
|
+
name: string;
|
|
249
|
+
path: string;
|
|
250
|
+
}>;
|
|
251
|
+
export type LocalSkill = z.infer<typeof zLocalSkill>;
|
|
252
|
+
export declare const zGitHubSkill: z.ZodObject<{
|
|
253
|
+
name: z.ZodString;
|
|
254
|
+
description: z.ZodString;
|
|
255
|
+
} & {
|
|
256
|
+
type: z.ZodLiteral<"github">;
|
|
257
|
+
repo: z.ZodString;
|
|
258
|
+
path: z.ZodOptional<z.ZodString>;
|
|
259
|
+
}, "strip", z.ZodTypeAny, {
|
|
260
|
+
type: "github";
|
|
261
|
+
description: string;
|
|
262
|
+
name: string;
|
|
263
|
+
repo: string;
|
|
264
|
+
path?: string | undefined;
|
|
265
|
+
}, {
|
|
266
|
+
type: "github";
|
|
267
|
+
description: string;
|
|
268
|
+
name: string;
|
|
269
|
+
repo: string;
|
|
270
|
+
path?: string | undefined;
|
|
271
|
+
}>;
|
|
272
|
+
export type GitHubSkill = z.infer<typeof zGitHubSkill>;
|
|
273
|
+
export declare const zSkill: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
274
|
+
name: z.ZodString;
|
|
275
|
+
description: z.ZodString;
|
|
276
|
+
} & {
|
|
277
|
+
type: z.ZodLiteral<"local">;
|
|
278
|
+
path: z.ZodString;
|
|
279
|
+
}, "strip", z.ZodTypeAny, {
|
|
280
|
+
type: "local";
|
|
281
|
+
description: string;
|
|
282
|
+
name: string;
|
|
283
|
+
path: string;
|
|
284
|
+
}, {
|
|
285
|
+
type: "local";
|
|
286
|
+
description: string;
|
|
287
|
+
name: string;
|
|
288
|
+
path: string;
|
|
289
|
+
}>, z.ZodObject<{
|
|
290
|
+
name: z.ZodString;
|
|
291
|
+
description: z.ZodString;
|
|
292
|
+
} & {
|
|
293
|
+
type: z.ZodLiteral<"github">;
|
|
294
|
+
repo: z.ZodString;
|
|
295
|
+
path: z.ZodOptional<z.ZodString>;
|
|
296
|
+
}, "strip", z.ZodTypeAny, {
|
|
297
|
+
type: "github";
|
|
298
|
+
description: string;
|
|
299
|
+
name: string;
|
|
300
|
+
repo: string;
|
|
301
|
+
path?: string | undefined;
|
|
302
|
+
}, {
|
|
303
|
+
type: "github";
|
|
304
|
+
description: string;
|
|
305
|
+
name: string;
|
|
306
|
+
repo: string;
|
|
307
|
+
path?: string | undefined;
|
|
308
|
+
}>]>;
|
|
309
|
+
export type Skill = z.infer<typeof zSkill>;
|
|
310
|
+
export declare const zSkillMap: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
311
|
+
name: z.ZodString;
|
|
312
|
+
description: z.ZodString;
|
|
313
|
+
} & {
|
|
314
|
+
type: z.ZodLiteral<"local">;
|
|
315
|
+
path: z.ZodString;
|
|
316
|
+
}, "strip", z.ZodTypeAny, {
|
|
317
|
+
type: "local";
|
|
318
|
+
description: string;
|
|
319
|
+
name: string;
|
|
320
|
+
path: string;
|
|
321
|
+
}, {
|
|
322
|
+
type: "local";
|
|
323
|
+
description: string;
|
|
324
|
+
name: string;
|
|
325
|
+
path: string;
|
|
326
|
+
}>, z.ZodObject<{
|
|
327
|
+
name: z.ZodString;
|
|
328
|
+
description: z.ZodString;
|
|
329
|
+
} & {
|
|
330
|
+
type: z.ZodLiteral<"github">;
|
|
331
|
+
repo: z.ZodString;
|
|
332
|
+
path: z.ZodOptional<z.ZodString>;
|
|
333
|
+
}, "strip", z.ZodTypeAny, {
|
|
334
|
+
type: "github";
|
|
335
|
+
description: string;
|
|
336
|
+
name: string;
|
|
337
|
+
repo: string;
|
|
338
|
+
path?: string | undefined;
|
|
339
|
+
}, {
|
|
340
|
+
type: "github";
|
|
341
|
+
description: string;
|
|
342
|
+
name: string;
|
|
343
|
+
repo: string;
|
|
344
|
+
path?: string | undefined;
|
|
345
|
+
}>]>>;
|
|
346
|
+
export type SkillMap = z.infer<typeof zSkillMap>;
|
|
347
|
+
export interface SkillsFlags {
|
|
348
|
+
enabledSkills?: Set<string> | null;
|
|
349
|
+
disabledSkills?: Set<string> | null;
|
|
350
|
+
}
|
|
351
|
+
export interface ServerContextWithOctokit extends Record<string, unknown> {
|
|
352
|
+
octokit?: Octokit | null;
|
|
353
|
+
}
|
|
354
|
+
export declare const zViewSkillInputSchema: {
|
|
355
|
+
readonly skill_name: z.ZodString;
|
|
356
|
+
readonly path: z.ZodString;
|
|
357
|
+
};
|
|
358
|
+
export type ViewSkillInputSchema = InferSchema<typeof zViewSkillInputSchema>;
|
|
359
|
+
export declare const zViewSkillOutputSchema: {
|
|
360
|
+
readonly content: z.ZodString;
|
|
361
|
+
};
|
|
362
|
+
export type ViewSkillOutputSchema = InferSchema<typeof zViewSkillOutputSchema>;
|
|
363
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const zSkillType = z.enum([
|
|
3
|
+
'local',
|
|
4
|
+
'local_collection',
|
|
5
|
+
'github',
|
|
6
|
+
'github_collection',
|
|
7
|
+
]);
|
|
8
|
+
export const zLocalSkillCfg = z.object({
|
|
9
|
+
type: z.literal('local'),
|
|
10
|
+
path: z.string(),
|
|
11
|
+
});
|
|
12
|
+
const zCollectionFlagsCfg = z.object({
|
|
13
|
+
enabled_skills: z.array(z.string()).optional(),
|
|
14
|
+
disabled_skills: z.array(z.string()).optional(),
|
|
15
|
+
ignored_paths: z.array(z.string()).optional(),
|
|
16
|
+
});
|
|
17
|
+
export const zLocalCollectionSkillCfg = zCollectionFlagsCfg.extend({
|
|
18
|
+
type: z.literal('local_collection'),
|
|
19
|
+
path: z.string(),
|
|
20
|
+
});
|
|
21
|
+
export const zGitHubSkillCfg = z.object({
|
|
22
|
+
type: z.literal('github'),
|
|
23
|
+
repo: z.string(),
|
|
24
|
+
path: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
export const zGitHubCollectionSkillCfg = zCollectionFlagsCfg.extend({
|
|
27
|
+
type: z.literal('github_collection'),
|
|
28
|
+
repo: z.string(),
|
|
29
|
+
path: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
export const zSkillCfg = z.discriminatedUnion('type', [
|
|
32
|
+
zLocalSkillCfg,
|
|
33
|
+
zLocalCollectionSkillCfg,
|
|
34
|
+
zGitHubSkillCfg,
|
|
35
|
+
zGitHubCollectionSkillCfg,
|
|
36
|
+
]);
|
|
37
|
+
export const zSkillCfgMap = z.record(zSkillCfg);
|
|
38
|
+
export const zSkillMatter = z.object({
|
|
39
|
+
name: z.string().trim().min(1),
|
|
40
|
+
description: z.string(),
|
|
41
|
+
});
|
|
42
|
+
export const zLocalSkill = zSkillMatter.extend(zLocalSkillCfg.shape);
|
|
43
|
+
export const zGitHubSkill = zSkillMatter.extend(zGitHubSkillCfg.shape);
|
|
44
|
+
export const zSkill = z.discriminatedUnion('type', [zLocalSkill, zGitHubSkill]);
|
|
45
|
+
export const zSkillMap = z.record(zSkill);
|
|
46
|
+
export const zViewSkillInputSchema = {
|
|
47
|
+
skill_name: z
|
|
48
|
+
.string()
|
|
49
|
+
.describe('The name of the skill to browse, or `.` to list all available skills.'),
|
|
50
|
+
path: z.string().describe(`
|
|
51
|
+
A relative path to a file or directory within the skill to view.
|
|
52
|
+
If empty, will view the \`SKILL.md\` file by default.
|
|
53
|
+
Use \`.\` to list the root directory of the skill.
|
|
54
|
+
`.trim()),
|
|
55
|
+
};
|
|
56
|
+
export const zViewSkillOutputSchema = {
|
|
57
|
+
content: z.string().describe('The content of the file or directory listing.'),
|
|
58
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Octokit } from '@octokit/rest';
|
|
2
|
+
import { type McpFeatureFlags } from '@tigerdata/mcp-boilerplate';
|
|
3
|
+
import { type CollectionFlags, type CollectionFlagsCfg, type Skill, type SkillCfgMap, type SkillMatter, type SkillsFlags } from './types.js';
|
|
4
|
+
export declare const getSkillConfig: (configFilePath?: string) => Promise<SkillCfgMap>;
|
|
5
|
+
export declare const parseSkillFile: (fileContent: string) => Promise<{
|
|
6
|
+
matter: SkillMatter;
|
|
7
|
+
content: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const loadSkills: ({ octokit, force, }?: {
|
|
10
|
+
octokit?: Octokit | null;
|
|
11
|
+
force?: boolean;
|
|
12
|
+
}) => Promise<Map<string, Skill>>;
|
|
13
|
+
export declare const resolveSkill: ({ name, octokit, flags, force, }: {
|
|
14
|
+
name: string;
|
|
15
|
+
octokit?: Octokit | null;
|
|
16
|
+
flags?: SkillsFlags;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
}) => Promise<Skill | null>;
|
|
19
|
+
export declare const skillVisible: (name: string, flags: SkillsFlags) => boolean;
|
|
20
|
+
export declare const listSkills: ({ octokit, flags, force, }?: {
|
|
21
|
+
octokit?: Octokit | null;
|
|
22
|
+
flags?: SkillsFlags;
|
|
23
|
+
force?: boolean;
|
|
24
|
+
}) => Promise<string>;
|
|
25
|
+
export declare const viewSkillContent: ({ octokit, flags, name, path: passedPath, }: {
|
|
26
|
+
octokit?: Octokit | null;
|
|
27
|
+
flags?: SkillsFlags;
|
|
28
|
+
name: string;
|
|
29
|
+
path?: string;
|
|
30
|
+
}) => Promise<string>;
|
|
31
|
+
export declare const skillsDescription: string;
|
|
32
|
+
export declare const parseSkillsFlags: (query: McpFeatureFlags["query"]) => SkillsFlags;
|
|
33
|
+
export declare const parseCollectionFlags: (cfg: CollectionFlagsCfg) => CollectionFlags;
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import Path from 'node:path';
|
|
3
|
+
import { log, } from '@tigerdata/mcp-boilerplate';
|
|
4
|
+
import { encode } from '@toon-format/toon';
|
|
5
|
+
import matter from 'gray-matter';
|
|
6
|
+
import YAML from 'yaml';
|
|
7
|
+
import { zSkillCfgMap, zSkillMatter, } from './types.js';
|
|
8
|
+
const TTL = process.env.SKILLS_TTL
|
|
9
|
+
? parseInt(process.env.SKILLS_TTL, 10)
|
|
10
|
+
: 5 * 60 * 1000;
|
|
11
|
+
let lastFetchCfg = 0;
|
|
12
|
+
let skillCfgMap = null;
|
|
13
|
+
export const getSkillConfig = async (configFilePath = process.env.SKILLS_FILE || './skills.yaml') => {
|
|
14
|
+
if (skillCfgMap && Date.now() - lastFetchCfg < TTL)
|
|
15
|
+
return skillCfgMap;
|
|
16
|
+
const data = await readFile(configFilePath, 'utf-8');
|
|
17
|
+
skillCfgMap = zSkillCfgMap.parse(YAML.parse(data));
|
|
18
|
+
lastFetchCfg = Date.now();
|
|
19
|
+
return skillCfgMap;
|
|
20
|
+
};
|
|
21
|
+
export const parseSkillFile = async (fileContent) => {
|
|
22
|
+
const { data, content } = matter(fileContent);
|
|
23
|
+
const skillMatter = zSkillMatter.parse(data);
|
|
24
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(skillMatter.name)) {
|
|
25
|
+
const normalized = skillMatter.name
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/\s+/g, '-')
|
|
28
|
+
.replace(/[^a-z0-9-_]/g, '_')
|
|
29
|
+
.replace(/-[-_]+/g, '-')
|
|
30
|
+
.replace(/_[_-]+/g, '_')
|
|
31
|
+
.replace(/(^[-_]+)|([-_]+$)/g, '');
|
|
32
|
+
log.warn(`Skill name "${skillMatter.name}" contains invalid characters. Normalizing to "${normalized}".`);
|
|
33
|
+
skillMatter.name = normalized;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
matter: skillMatter,
|
|
37
|
+
content,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
// skill name/path -> content
|
|
41
|
+
const skillContentCache = new Map();
|
|
42
|
+
let lastFetchSkillMap = 0;
|
|
43
|
+
let skillMap = null;
|
|
44
|
+
export const loadSkills = async ({ octokit, force = false, } = {}) => {
|
|
45
|
+
if (skillMap && !force && Date.now() - lastFetchSkillMap < TTL)
|
|
46
|
+
return skillMap;
|
|
47
|
+
skillMap = doLoadSkills(octokit).then((result) => {
|
|
48
|
+
lastFetchSkillMap = Date.now();
|
|
49
|
+
return result;
|
|
50
|
+
}, (err) => {
|
|
51
|
+
log.error('Failed to load skills, returning empty skill map', err);
|
|
52
|
+
skillMap = null;
|
|
53
|
+
return new Map();
|
|
54
|
+
});
|
|
55
|
+
return skillMap;
|
|
56
|
+
};
|
|
57
|
+
const doLoadSkills = async (octokit) => {
|
|
58
|
+
const skillCfgs = await getSkillConfig();
|
|
59
|
+
skillContentCache.clear();
|
|
60
|
+
const skills = new Map();
|
|
61
|
+
const alreadyExists = (name, path, description) => {
|
|
62
|
+
const existing = skills.get(name);
|
|
63
|
+
if (existing) {
|
|
64
|
+
log.warn(`Skill with name "${name}" already loaded from path "${existing.path}". Skipping duplicate at path "${path}".`, { existing, duplicate: { path, name, description } });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
69
|
+
const shouldIgnorePath = (path, flags) => {
|
|
70
|
+
if (flags?.ignoredPaths?.has(path)) {
|
|
71
|
+
log.debug(`Ignoring path "${path}" in ignoredPaths`);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
const shouldIgnoreSkill = (name, flags) => {
|
|
77
|
+
if (flags?.enabledSkills && !flags.enabledSkills.has(name)) {
|
|
78
|
+
log.debug(`Ignoring skill "${name}" not in enabledSkills`);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (flags?.disabledSkills?.has(name)) {
|
|
82
|
+
log.debug(`Ignoring skill "${name}" in disabledSkills`);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
const loadLocalPath = async (path, flags) => {
|
|
88
|
+
if (shouldIgnorePath(path, flags))
|
|
89
|
+
return;
|
|
90
|
+
const skillPath = `${path}/SKILL.md`;
|
|
91
|
+
try {
|
|
92
|
+
const fileContent = await readFile(skillPath, 'utf-8');
|
|
93
|
+
const { matter: { name, description }, content, } = await parseSkillFile(fileContent);
|
|
94
|
+
if (shouldIgnoreSkill(name, flags))
|
|
95
|
+
return;
|
|
96
|
+
if (alreadyExists(name, path, description))
|
|
97
|
+
return;
|
|
98
|
+
skills.set(name, {
|
|
99
|
+
type: 'local',
|
|
100
|
+
path,
|
|
101
|
+
name,
|
|
102
|
+
description,
|
|
103
|
+
});
|
|
104
|
+
skillContentCache.set(`${name}/SKILL.md`, content);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
log.error(`Failed to load skill at path: ${skillPath}`, err);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const loadGitHubPath = async (owner, repo, path, flags) => {
|
|
111
|
+
if (shouldIgnorePath(path, flags))
|
|
112
|
+
return;
|
|
113
|
+
const skillPath = `${path}/SKILL.md`;
|
|
114
|
+
if (!octokit) {
|
|
115
|
+
log.error(`Octokit instance is required to load GitHub skills: ${owner}/${repo}/${skillPath}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const skillFileResponse = await octokit.repos.getContent({
|
|
120
|
+
owner,
|
|
121
|
+
repo,
|
|
122
|
+
path: skillPath,
|
|
123
|
+
});
|
|
124
|
+
if (Array.isArray(skillFileResponse.data) ||
|
|
125
|
+
skillFileResponse.data.type !== 'file') {
|
|
126
|
+
log.error(`Expected SKILL.md to be a file`, null, {
|
|
127
|
+
owner,
|
|
128
|
+
repo,
|
|
129
|
+
path: skillPath,
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const fileContent = Buffer.from(skillFileResponse.data.content, 'base64').toString('utf-8');
|
|
134
|
+
const { matter: { name, description }, content, } = await parseSkillFile(fileContent);
|
|
135
|
+
if (shouldIgnoreSkill(name, flags))
|
|
136
|
+
return;
|
|
137
|
+
if (alreadyExists(name, path, description))
|
|
138
|
+
return;
|
|
139
|
+
skills.set(name, {
|
|
140
|
+
type: 'github',
|
|
141
|
+
repo: `${owner}/${repo}`,
|
|
142
|
+
path,
|
|
143
|
+
name,
|
|
144
|
+
description,
|
|
145
|
+
});
|
|
146
|
+
skillContentCache.set(`${name}/SKILL.md`, content);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
log.error(`Failed to load skill at GitHub path: ${owner}/${repo}/${skillPath}\n${err.message}`);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const promises = [];
|
|
153
|
+
await Promise.all(Object.entries(skillCfgs).map(async ([name, cfg]) => {
|
|
154
|
+
try {
|
|
155
|
+
switch (cfg.type) {
|
|
156
|
+
case 'local': {
|
|
157
|
+
promises.push(loadLocalPath(cfg.path));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'local_collection': {
|
|
161
|
+
const dirEntries = await readdir(cfg.path, {
|
|
162
|
+
withFileTypes: true,
|
|
163
|
+
});
|
|
164
|
+
const flags = parseCollectionFlags(cfg);
|
|
165
|
+
for (const entry of dirEntries) {
|
|
166
|
+
if (entry.isFile())
|
|
167
|
+
continue;
|
|
168
|
+
if (!entry.isDirectory()) {
|
|
169
|
+
log.warn(`Skipping non-directory entry in local_collection`, {
|
|
170
|
+
path: `${cfg.path}/${entry.name}`,
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
promises.push(loadLocalPath(`${cfg.path}/${entry.name}`, flags));
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case 'github': {
|
|
179
|
+
const [owner, repo] = cfg.repo.split('/');
|
|
180
|
+
if (!owner || !repo) {
|
|
181
|
+
log.error(`Invalid GitHub repo format in skill config: ${cfg.repo}`, null, { name, repo: cfg.repo });
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
promises.push(loadGitHubPath(owner, repo, cfg.path || '.'));
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'github_collection': {
|
|
188
|
+
const [owner, repo] = cfg.repo.split('/');
|
|
189
|
+
if (!owner || !repo) {
|
|
190
|
+
log.error(`Invalid GitHub repo format in skill config: ${cfg.repo}`, null, { name, repo: cfg.repo });
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (!octokit) {
|
|
194
|
+
log.error(`Octokit instance is required to load GitHub collection skills: ${cfg.repo}`);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const rootPath = cfg.path
|
|
198
|
+
? cfg.path.replace(/(^\.?\/+)|(^\.$)|(\/\.$)/g, '')
|
|
199
|
+
: '';
|
|
200
|
+
const dirResponse = await octokit.repos.getContent({
|
|
201
|
+
owner,
|
|
202
|
+
repo,
|
|
203
|
+
path: rootPath,
|
|
204
|
+
});
|
|
205
|
+
if (!Array.isArray(dirResponse.data)) {
|
|
206
|
+
log.error(`Expected github_collection repo path to be a directory`, null, { name, owner, repo, path: cfg.path || '.' });
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const flags = parseCollectionFlags(cfg);
|
|
210
|
+
for (const entry of dirResponse.data) {
|
|
211
|
+
if (entry.type === 'file')
|
|
212
|
+
continue;
|
|
213
|
+
if (entry.type !== 'dir') {
|
|
214
|
+
log.warn(`Skipping non-directory entry in github_collection`, {
|
|
215
|
+
path: `${cfg.repo}/${entry.path}`,
|
|
216
|
+
type: entry.type,
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
promises.push(loadGitHubPath(owner, repo, entry.path, flags));
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
default: {
|
|
225
|
+
// @ts-expect-error exhaustive check
|
|
226
|
+
throw new Error(`Unhandled skill config type: ${cfg.type}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
log.error(`Failed to load skill config "${name}"`, err);
|
|
232
|
+
}
|
|
233
|
+
}));
|
|
234
|
+
await Promise.all(promises);
|
|
235
|
+
// Sort skills by name
|
|
236
|
+
return new Map(Array.from(skills.entries()).sort((a, b) => a[0].localeCompare(b[0])));
|
|
237
|
+
};
|
|
238
|
+
export const resolveSkill = async ({ name, octokit, flags = {}, force = false, }) => {
|
|
239
|
+
if (!skillVisible(name, flags)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const skills = await loadSkills({ octokit, force });
|
|
243
|
+
return skills.get(name) || null;
|
|
244
|
+
};
|
|
245
|
+
export const skillVisible = (name, flags) => {
|
|
246
|
+
if (flags.enabledSkills && !flags.enabledSkills.has(name)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (flags.disabledSkills?.has(name)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
};
|
|
254
|
+
export const listSkills = async ({ octokit, flags = {}, force = false, } = {}) => {
|
|
255
|
+
const skills = await loadSkills({ octokit, force });
|
|
256
|
+
return `<available_skills>
|
|
257
|
+
${encode([...skills.values()]
|
|
258
|
+
.filter((s) => skillVisible(s.name, flags))
|
|
259
|
+
.map((s) => ({
|
|
260
|
+
name: s.name,
|
|
261
|
+
description: s.description,
|
|
262
|
+
})), { delimiter: '\t' })}
|
|
263
|
+
</available_skills>`;
|
|
264
|
+
};
|
|
265
|
+
export const viewSkillContent = async ({ octokit, flags = {}, name, path: passedPath, }) => {
|
|
266
|
+
const skill = await resolveSkill({ octokit, flags, name });
|
|
267
|
+
if (!skill) {
|
|
268
|
+
throw new Error(`Skill not found: ${name}`);
|
|
269
|
+
}
|
|
270
|
+
const targetPath = passedPath || 'SKILL.md';
|
|
271
|
+
const cacheKey = `${name}/${normalizeSkillPath(targetPath)}`;
|
|
272
|
+
const cached = skillContentCache.get(cacheKey);
|
|
273
|
+
if (cached) {
|
|
274
|
+
return cached;
|
|
275
|
+
}
|
|
276
|
+
const content = await getSkillContent({ octokit, skill, path: targetPath });
|
|
277
|
+
skillContentCache.set(cacheKey, content);
|
|
278
|
+
return content;
|
|
279
|
+
};
|
|
280
|
+
const normalizeSkillPath = (path) => {
|
|
281
|
+
const normalizedPath = Path.posix
|
|
282
|
+
.normalize(
|
|
283
|
+
// treat \ as /
|
|
284
|
+
path.replace(/\\/g, '/'))
|
|
285
|
+
// strip leading ./ or /
|
|
286
|
+
.replace(/^(\.?\/)+/, '');
|
|
287
|
+
if (normalizedPath.split('/').some((s) => s === '..') ||
|
|
288
|
+
normalizedPath.includes('\0')) {
|
|
289
|
+
throw new Error(`Invalid path: ${path}`);
|
|
290
|
+
}
|
|
291
|
+
return normalizedPath;
|
|
292
|
+
};
|
|
293
|
+
const getSkillContent = async ({ skill, path: targetPath, octokit, }) => {
|
|
294
|
+
const normalizedPath = normalizeSkillPath(targetPath);
|
|
295
|
+
switch (skill.type) {
|
|
296
|
+
case 'local': {
|
|
297
|
+
const root = Path.resolve(skill.path);
|
|
298
|
+
const target = Path.resolve(Path.join(root, normalizedPath));
|
|
299
|
+
if (targetPath !== '.' && !target.startsWith(root)) {
|
|
300
|
+
throw new Error(`Invalid path: ${targetPath}`);
|
|
301
|
+
}
|
|
302
|
+
const s = await stat(target).catch(() => {
|
|
303
|
+
throw new Error(`Path not found: ${targetPath}`);
|
|
304
|
+
});
|
|
305
|
+
if (s.isDirectory()) {
|
|
306
|
+
const entries = await readdir(target, {
|
|
307
|
+
withFileTypes: true,
|
|
308
|
+
});
|
|
309
|
+
const listing = entries
|
|
310
|
+
.map((entry) => {
|
|
311
|
+
return `${entry.isDirectory() ? '📁' : '📄'} ${entry.name}`;
|
|
312
|
+
})
|
|
313
|
+
.join('\n');
|
|
314
|
+
return `Directory listing for ${skill.name}/${normalizedPath}:\n${listing}`;
|
|
315
|
+
}
|
|
316
|
+
else if (s.isFile()) {
|
|
317
|
+
return await readFile(target, 'utf-8');
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
throw new Error(`Unsupported file type at path: ${target}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
case 'github': {
|
|
324
|
+
const [owner, repo] = skill.repo.split('/');
|
|
325
|
+
if (!owner || !repo) {
|
|
326
|
+
throw new Error(`Invalid GitHub repo format in skill: ${skill.repo}`);
|
|
327
|
+
}
|
|
328
|
+
const path = `${skill.path || '.'}/${normalizedPath}`
|
|
329
|
+
.replace(/\/+/g, '/')
|
|
330
|
+
.replace(/(^\.?\/+)|(^\.$)|(\/\.$)/g, '');
|
|
331
|
+
if (!octokit) {
|
|
332
|
+
throw new Error(`Octokit instance is required to load GitHub skill content: ${owner}/${repo}/${path}`);
|
|
333
|
+
}
|
|
334
|
+
const response = await octokit.repos.getContent({
|
|
335
|
+
owner,
|
|
336
|
+
repo,
|
|
337
|
+
path,
|
|
338
|
+
});
|
|
339
|
+
if (Array.isArray(response.data)) {
|
|
340
|
+
// Directory listing
|
|
341
|
+
const listing = response.data
|
|
342
|
+
.map((entry) => {
|
|
343
|
+
return `${entry.type === 'dir' ? '📁' : '📄'} ${entry.name}`;
|
|
344
|
+
})
|
|
345
|
+
.join('\n');
|
|
346
|
+
return `Directory listing for ${skill.name}/${normalizedPath}:\n${listing}`;
|
|
347
|
+
}
|
|
348
|
+
if (response.data.type !== 'file') {
|
|
349
|
+
throw new Error(`Unsupported content type: ${response.data.type}`);
|
|
350
|
+
}
|
|
351
|
+
return Buffer.from(response.data.content, 'base64').toString('utf-8');
|
|
352
|
+
}
|
|
353
|
+
default: {
|
|
354
|
+
// @ts-expect-error exhaustive check
|
|
355
|
+
throw new Error(`Unhandled skill type: ${skill.type}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
export const skillsDescription = `
|
|
360
|
+
# Skills
|
|
361
|
+
|
|
362
|
+
This tool provides access to domain-specific skills - structured knowledge and procedures for specialized tasks.
|
|
363
|
+
|
|
364
|
+
## How to Use Skills
|
|
365
|
+
|
|
366
|
+
1. **Discover**: If you have not been provided the list of skills, fetch them by invoking this tool with \`name: "."\`
|
|
367
|
+
2. **Read**: Access a skill by reading its SKILL.md file: \`name: "skill-name", path: "SKILL.md"\`
|
|
368
|
+
3. **Explore**: Navigate within the skill directory to find additional resources, examples, or templates.
|
|
369
|
+
The SKILL.md file and other documents may contain relative links to guide you.
|
|
370
|
+
You can list the content of directories by specifying the directory path, relative to the skill root.
|
|
371
|
+
4. **Apply**: Follow the procedures and reference the knowledge in the skill to complete your task
|
|
372
|
+
|
|
373
|
+
## Skill Structure
|
|
374
|
+
|
|
375
|
+
Each skill contains:
|
|
376
|
+
- **SKILL.md**: Main documentation (always start here)
|
|
377
|
+
- **REFERENCE.md**: Quick reference card (optional)
|
|
378
|
+
- Additional files: Examples, templates, scripts
|
|
379
|
+
|
|
380
|
+
## When to Use Skills
|
|
381
|
+
|
|
382
|
+
- Read relevant skills proactively when you identify a task that matches a skill's domain
|
|
383
|
+
- Skills are meant to augment your knowledge, not replace your reasoning
|
|
384
|
+
- Apply skill guidance while adapting to the specific user request
|
|
385
|
+
- Skills use progressive disclosure - start with high-level guidance and drill down only as needed
|
|
386
|
+
- Skills may also refer to tools or resources external to the skill itself
|
|
387
|
+
- Use other tools to execute any code or scripts provided by the skill
|
|
388
|
+
`.trim();
|
|
389
|
+
const toSet = (flag) => flag
|
|
390
|
+
? Array.isArray(flag)
|
|
391
|
+
? new Set(flag)
|
|
392
|
+
: typeof flag === 'string'
|
|
393
|
+
? new Set(flag.split(',').map((s) => s.trim()))
|
|
394
|
+
: null
|
|
395
|
+
: null;
|
|
396
|
+
export const parseSkillsFlags = (query) => ({
|
|
397
|
+
enabledSkills: toSet(query?.enabled_skills),
|
|
398
|
+
disabledSkills: toSet(query?.disabled_skills),
|
|
399
|
+
});
|
|
400
|
+
export const parseCollectionFlags = (cfg) => ({
|
|
401
|
+
enabledSkills: toSet(cfg.enabled_skills),
|
|
402
|
+
disabledSkills: toSet(cfg.disabled_skills),
|
|
403
|
+
ignoredPaths: toSet(cfg.ignored_paths),
|
|
404
|
+
});
|
package/dist/stdio.js
CHANGED
|
@@ -6,7 +6,7 @@ export const stdioServerFactory = async ({ name, version, context, apiFactories,
|
|
|
6
6
|
try {
|
|
7
7
|
console.error('Starting default (STDIO) server...');
|
|
8
8
|
const transport = new StdioServerTransport();
|
|
9
|
-
const { server } = mcpServerFactory({
|
|
9
|
+
const { server } = await mcpServerFactory({
|
|
10
10
|
name,
|
|
11
11
|
version,
|
|
12
12
|
context,
|
package/dist/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface BaseApiDefinition {
|
|
|
18
18
|
fn(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
19
19
|
pickResult?(result: Record<string, unknown>): unknown;
|
|
20
20
|
}
|
|
21
|
-
export type BaseApiFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => BaseApiDefinition
|
|
21
|
+
export type BaseApiFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => BaseApiDefinition | Promise<BaseApiDefinition>;
|
|
22
22
|
export type BasePromptConfig = {
|
|
23
23
|
title?: string;
|
|
24
24
|
description?: string;
|
|
@@ -30,7 +30,7 @@ export interface BasePromptDefinition {
|
|
|
30
30
|
disabled?: boolean;
|
|
31
31
|
fn(args: Record<string, unknown>): Promise<GetPromptResult>;
|
|
32
32
|
}
|
|
33
|
-
export type BasePromptFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => BasePromptDefinition
|
|
33
|
+
export type BasePromptFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => BasePromptDefinition | Promise<BasePromptDefinition>;
|
|
34
34
|
export type ToolConfig<InputArgs extends ZodRawShape, OutputArgs extends ZodRawShape> = {
|
|
35
35
|
title?: string;
|
|
36
36
|
description?: string;
|
|
@@ -43,7 +43,7 @@ export interface ApiDefinition<InputArgs extends ZodRawShape, OutputArgs extends
|
|
|
43
43
|
fn(args: z.objectOutputType<InputArgs, ZodTypeAny>): Promise<z.objectOutputType<OutputArgs, ZodTypeAny>>;
|
|
44
44
|
pickResult?(result: z.objectOutputType<OutputArgs, ZodTypeAny>): SimplifiedOutputArgs;
|
|
45
45
|
}
|
|
46
|
-
export type ApiFactory<Context extends Record<string, unknown>, Input extends ZodRawShape, Output extends ZodRawShape, RestOutput = Output> = (ctx: Context, featureFlags: McpFeatureFlags) => ApiDefinition<Input, Output, RestOutput
|
|
46
|
+
export type ApiFactory<Context extends Record<string, unknown>, Input extends ZodRawShape, Output extends ZodRawShape, RestOutput = Output> = (ctx: Context, featureFlags: McpFeatureFlags) => ApiDefinition<Input, Output, RestOutput> | Promise<ApiDefinition<Input, Output, RestOutput>>;
|
|
47
47
|
export type RouterFactoryResult = [Router, () => void | Promise<void>];
|
|
48
48
|
export type PromptConfig<InputArgs extends ZodRawShape> = {
|
|
49
49
|
title?: string;
|
|
@@ -76,7 +76,7 @@ export interface StaticResourceDefinition {
|
|
|
76
76
|
read: ReadResourceCallback;
|
|
77
77
|
}
|
|
78
78
|
export type ResourceDefinition = TemplatedResourceDefinition | StaticResourceDefinition;
|
|
79
|
-
export type ResourceFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => ResourceDefinition
|
|
79
|
+
export type ResourceFactory<Context extends Record<string, unknown>> = (ctx: Context, featureFlags: McpFeatureFlags) => ResourceDefinition | Promise<ResourceDefinition>;
|
|
80
80
|
export interface ParsedQs {
|
|
81
81
|
[key: string]: undefined | string | ParsedQs | (string | ParsedQs)[];
|
|
82
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tigerdata/mcp-boilerplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "MCP boilerplate code for Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "TigerData",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"./instrumentation": {
|
|
25
25
|
"import": "./dist/instrumentation.js",
|
|
26
26
|
"types": "./dist/instrumentation.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./skills": {
|
|
29
|
+
"import": "./dist/skills/index.js",
|
|
30
|
+
"types": "./dist/skills/index.d.ts"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"files": [
|
|
@@ -36,7 +40,7 @@
|
|
|
36
40
|
"lint": "./bun x @biomejs/biome check"
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|
|
39
|
-
"@mcp-use/inspector": "^0.
|
|
43
|
+
"@mcp-use/inspector": "^0.14.0",
|
|
40
44
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
41
45
|
"@opentelemetry/api": "^1.9.0",
|
|
42
46
|
"@opentelemetry/auto-instrumentations-node": "^0.67.3",
|
|
@@ -46,17 +50,21 @@
|
|
|
46
50
|
"@opentelemetry/sdk-node": "^0.208.0",
|
|
47
51
|
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
|
48
52
|
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
53
|
+
"@toon-format/toon": "^2.1.0",
|
|
49
54
|
"express": "^5.2.1",
|
|
55
|
+
"gray-matter": "^4.0.3",
|
|
50
56
|
"raw-body": "^3.0.2",
|
|
57
|
+
"yaml": "^2.8.2",
|
|
51
58
|
"zod": "^3.23.8"
|
|
52
59
|
},
|
|
53
60
|
"devDependencies": {
|
|
54
|
-
"@biomejs/biome": "^2.3.
|
|
61
|
+
"@biomejs/biome": "^2.3.11",
|
|
62
|
+
"@octokit/rest": "^22.0.1",
|
|
55
63
|
"@types/bun": "^1.3.5",
|
|
56
64
|
"@types/express": "^5.0.6",
|
|
57
65
|
"@types/node": "^22.19.3",
|
|
58
|
-
"@typescript-eslint/typescript-estree": "^8.
|
|
59
|
-
"ai": "^5.0.
|
|
66
|
+
"@typescript-eslint/typescript-estree": "^8.52.0",
|
|
67
|
+
"ai": "^5.0.118",
|
|
60
68
|
"eslint": "^9.39.2",
|
|
61
69
|
"typescript": "^5.9.3"
|
|
62
70
|
},
|