create-knowledge-mcp 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +21 -2
- package/index.js +153 -0
- package/package.json +40 -4
- package/template/.env.example +35 -0
- package/template/Dockerfile +26 -0
- package/template/README.md +75 -0
- package/template/_dockerignore +9 -0
- package/template/_gitignore +9 -0
- package/template/package.json +26 -0
- package/template/src/plugins/notes.ts +44 -0
- package/template/src/server.ts +11 -0
- package/template/tsconfig.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 chanthr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-knowledge-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffolds your own [Knowledge MCP](https://github.com/chanthr/internal-mcp) server.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx create-knowledge-mcp my-mcp
|
|
7
|
+
# or: npm create knowledge-mcp@latest my-mcp
|
|
8
|
+
cd my-mcp
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
You get `src/server.ts`, an example plugin in `src/plugins/notes.ts`, `.env`, a `Dockerfile` and a README.
|
|
13
|
+
Add a tool by adding a file to `src/plugins/`.
|
|
14
|
+
|
|
15
|
+
| Option | Meaning |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `--no-install` | Skip installing packages |
|
|
18
|
+
| `--no-git` | Skip `git init` |
|
|
19
|
+
|
|
20
|
+
Installs with the package manager that ran it (npm, pnpm, yarn or bun). Requires Node 22.13 or later.
|
|
21
|
+
|
|
22
|
+
MIT © chanthr
|
package/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-knowledge-mcp — scaffolds your own Knowledge MCP server.
|
|
4
|
+
*
|
|
5
|
+
* npx create-knowledge-mcp my-mcp # or: npm create knowledge-mcp@latest my-mcp
|
|
6
|
+
* npx create-knowledge-mcp my-mcp --no-install --no-git
|
|
7
|
+
*
|
|
8
|
+
* No dependencies: copies ./template, fills in the project name and the framework version, installs
|
|
9
|
+
* packages with the package manager that ran it (npm, pnpm, yarn or bun), and prints the next steps.
|
|
10
|
+
* KMCP_CORE_SPEC overrides the @chnxthr/knowledge-mcp dependency (tests use a local tarball).
|
|
11
|
+
*/
|
|
12
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { basename, join, resolve } from 'node:path';
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { createInterface } from 'node:readline/promises';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url));
|
|
19
|
+
const TEMPLATE = join(HERE, 'template');
|
|
20
|
+
const VERSION = JSON.parse(readFileSync(join(HERE, 'package.json'), 'utf8')).version;
|
|
21
|
+
const CORE = '@chnxthr/knowledge-mcp';
|
|
22
|
+
// npm drops .gitignore from published packages, so the template keeps it as _gitignore.
|
|
23
|
+
const RENAMES = { _gitignore: '.gitignore', _dockerignore: '.dockerignore' };
|
|
24
|
+
|
|
25
|
+
const tty = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
26
|
+
const bold = (s) => (tty ? `\x1b[1m${s}\x1b[22m` : s);
|
|
27
|
+
const dim = (s) => (tty ? `\x1b[2m${s}\x1b[22m` : s);
|
|
28
|
+
const green = (s) => (tty ? `\x1b[32m${s}\x1b[39m` : s);
|
|
29
|
+
|
|
30
|
+
function parseArgs(argv) {
|
|
31
|
+
const opts = { dir: undefined, install: true, git: true, help: false };
|
|
32
|
+
for (const a of argv) {
|
|
33
|
+
if (a === '--no-install') opts.install = false;
|
|
34
|
+
else if (a === '--no-git') opts.git = false;
|
|
35
|
+
else if (a === '-h' || a === '--help') opts.help = true;
|
|
36
|
+
else if (a.startsWith('-')) throw new Error(`unknown option ${a}`);
|
|
37
|
+
else if (!opts.dir) opts.dir = a;
|
|
38
|
+
else throw new Error(`unexpected argument ${a}`);
|
|
39
|
+
}
|
|
40
|
+
return opts;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** npm package name rules, simplified: lowercase, url-safe, no leading dot or underscore. */
|
|
44
|
+
export function toPackageName(dir) {
|
|
45
|
+
return (
|
|
46
|
+
basename(resolve(dir))
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9._~-]+/g, '-')
|
|
49
|
+
.replace(/^[._-]+|[-]+$/g, '') || 'my-knowledge-mcp'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Which package manager started us (npm_config_user_agent = "pnpm/9.0.0 npm/? node/v22 ..."). */
|
|
54
|
+
export function detectPackageManager(userAgent = process.env.npm_config_user_agent ?? '') {
|
|
55
|
+
const name = userAgent.split(' ')[0]?.split('/')[0];
|
|
56
|
+
return ['pnpm', 'yarn', 'bun'].includes(name) ? name : 'npm';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isEmptyDir(path) {
|
|
60
|
+
return !existsSync(path) || readdirSync(path).filter((f) => f !== '.git' && f !== '.DS_Store').length === 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fill(path, vars) {
|
|
64
|
+
let s = readFileSync(path, 'utf8');
|
|
65
|
+
for (const [k, v] of Object.entries(vars)) s = s.split(`__${k}__`).join(v);
|
|
66
|
+
writeFileSync(path, s);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Copy the template into `target` and fill placeholders. Exported for tests. */
|
|
70
|
+
export function scaffold(target, { name, coreSpec }) {
|
|
71
|
+
mkdirSync(target, { recursive: true });
|
|
72
|
+
cpSync(TEMPLATE, target, { recursive: true });
|
|
73
|
+
for (const [from, to] of Object.entries(RENAMES)) {
|
|
74
|
+
if (existsSync(join(target, from))) renameSync(join(target, from), join(target, to));
|
|
75
|
+
}
|
|
76
|
+
const vars = { NAME: name, CORE_SPEC: coreSpec, VERSION };
|
|
77
|
+
for (const f of ['package.json', 'README.md']) fill(join(target, f), vars);
|
|
78
|
+
if (!existsSync(join(target, '.env'))) cpSync(join(target, '.env.example'), join(target, '.env'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function ask(question, fallback) {
|
|
82
|
+
if (!process.stdin.isTTY) return fallback;
|
|
83
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
84
|
+
try {
|
|
85
|
+
const a = (await rl.question(`${question} ${dim(`(${fallback})`)} `)).trim();
|
|
86
|
+
return a || fallback;
|
|
87
|
+
} finally {
|
|
88
|
+
rl.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function run(cmd, args, cwd) {
|
|
93
|
+
const r = spawnSync(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
94
|
+
return r.status === 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
99
|
+
if (opts.help) {
|
|
100
|
+
console.log(`Usage: npx create-knowledge-mcp [dir] [--no-install] [--no-git]`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const dir = opts.dir ?? (await ask('Project folder?', 'my-knowledge-mcp'));
|
|
104
|
+
const target = resolve(dir);
|
|
105
|
+
if (!isEmptyDir(target)) throw new Error(`${target} is not empty — pick a new folder`);
|
|
106
|
+
|
|
107
|
+
const name = toPackageName(dir);
|
|
108
|
+
const coreSpec = process.env.KMCP_CORE_SPEC || `^${VERSION}`;
|
|
109
|
+
scaffold(target, { name, coreSpec });
|
|
110
|
+
console.log(`\n${green('✓')} Created ${bold(name)} in ${target}`);
|
|
111
|
+
|
|
112
|
+
const pm = detectPackageManager();
|
|
113
|
+
if (opts.install) {
|
|
114
|
+
console.log(dim(`\n${pm} install …`));
|
|
115
|
+
if (!run(pm, ['install'], target)) console.log(`\n${pm} install failed — run it yourself inside ${dir}.`);
|
|
116
|
+
}
|
|
117
|
+
if (opts.git && !existsSync(join(target, '.git'))) {
|
|
118
|
+
spawnSync('git', ['init', '-q'], { cwd: target, stdio: 'ignore' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const runScript = pm === 'npm' ? 'npm run' : pm;
|
|
122
|
+
const tsx = join(target, 'node_modules', '.bin', 'tsx');
|
|
123
|
+
console.log(`
|
|
124
|
+
${bold('Next')}
|
|
125
|
+
cd ${dir}
|
|
126
|
+
${runScript} dev ${dim('# http://localhost:8080/mcp, reloads on save')}
|
|
127
|
+
${runScript} keys -- create --email you@example.com --role admin ${dim('# prints an API key once')}
|
|
128
|
+
|
|
129
|
+
${bold('Add a tool')}
|
|
130
|
+
Copy src/plugins/notes.ts to src/plugins/<name>.ts and edit it. Every file there is loaded.
|
|
131
|
+
|
|
132
|
+
${bold('Use it from Claude Code (local, no key needed)')}
|
|
133
|
+
claude mcp add ${name} -- ${tsx} ${join(target, 'src', 'server.ts')} serve --stdio
|
|
134
|
+
|
|
135
|
+
${bold('Check what is on')}
|
|
136
|
+
${runScript} doctor
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Run only as a command (npx goes through a bin symlink, hence realpath), not when imported by tests.
|
|
141
|
+
const isMain = (() => {
|
|
142
|
+
try {
|
|
143
|
+
return !!process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
if (isMain) {
|
|
149
|
+
main().catch((e) => {
|
|
150
|
+
console.error(`create-knowledge-mcp: ${e instanceof Error ? e.message : e}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-knowledge-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Create your own Knowledge MCP server: npx create-knowledge-mcp my-mcp",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "chanthr",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/chanthr/internal-mcp.git",
|
|
11
|
+
"directory": "packages/create-knowledge-mcp"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/chanthr/internal-mcp#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/chanthr/internal-mcp/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"create",
|
|
21
|
+
"scaffold",
|
|
22
|
+
"mcp-server"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"create-knowledge-mcp": "index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"template",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test test/*.test.js"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.13.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# ── Server ─────────────────────────────────────────────────────────────────
|
|
2
|
+
PORT=8080
|
|
3
|
+
# MCP_TRANSPORT=stdio # or pass `serve --stdio`
|
|
4
|
+
# MCP_DATA_DIR=./data # SQLite store and checkpoints (default ./data)
|
|
5
|
+
# MCP_STORE=supabase # use Supabase instead of the SQLite file
|
|
6
|
+
# SUPABASE_URL=
|
|
7
|
+
# SUPABASE_SERVICE_ROLE_KEY=
|
|
8
|
+
|
|
9
|
+
# ── Your plugins ───────────────────────────────────────────────────────────
|
|
10
|
+
# Put the env vars your plugins read here (declare them in `env: [...]` so the tools stay hidden until set).
|
|
11
|
+
|
|
12
|
+
# ── Built-in connectors (each turns on when its vars are set) ───────────────
|
|
13
|
+
# Google Drive: one of these, plus drive.roots in mcp.config.json
|
|
14
|
+
# GOOGLE_SERVICE_ACCOUNT_KEY=
|
|
15
|
+
# GOOGLE_OAUTH_CLIENT_ID=
|
|
16
|
+
# GOOGLE_OAUTH_CLIENT_SECRET=
|
|
17
|
+
# GOOGLE_OAUTH_REFRESH_TOKEN=
|
|
18
|
+
# Semantic search over Drive
|
|
19
|
+
# QDRANT_URL=
|
|
20
|
+
# QDRANT_API_KEY=
|
|
21
|
+
# OPENAI_API_KEY=
|
|
22
|
+
# Slack (bot token for reading, user token adds search)
|
|
23
|
+
# SLACK_BOT_TOKEN=
|
|
24
|
+
# SLACK_USER_TOKEN=
|
|
25
|
+
# GitHub (Contents: read)
|
|
26
|
+
# GITHUB_TOKEN=
|
|
27
|
+
# Railway
|
|
28
|
+
# RAILWAY_TOKEN=
|
|
29
|
+
|
|
30
|
+
# ── Sign-in for Claude web/desktop connectors (optional, see OAUTH_SETUP.md) ──
|
|
31
|
+
# MCP_OAUTH_ISSUER=https://mcp.example.com
|
|
32
|
+
# OAUTH_GOOGLE_CLIENT_ID=
|
|
33
|
+
# OAUTH_GOOGLE_CLIENT_SECRET=
|
|
34
|
+
# OAUTH_JWT_SECRET=
|
|
35
|
+
# OAUTH_ALLOWED_HD=example.com
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Your Knowledge MCP server as a container.
|
|
2
|
+
# docker build -t my-mcp .
|
|
3
|
+
# docker run -d -p 8080:8080 -v my-mcp-data:/data --env-file .env my-mcp
|
|
4
|
+
# docker exec <container> node dist/server.js keys create --email you@example.com --role admin
|
|
5
|
+
FROM node:22-alpine AS build
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
COPY package*.json ./
|
|
8
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
9
|
+
COPY tsconfig.json ./
|
|
10
|
+
COPY src ./src
|
|
11
|
+
RUN npm run build && npm prune --omit=dev
|
|
12
|
+
|
|
13
|
+
FROM node:22-alpine
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
RUN addgroup -S app && adduser -S -G app -h /app app && mkdir -p /data && chown app:app /data
|
|
16
|
+
COPY --from=build --chown=app:app /app/node_modules ./node_modules
|
|
17
|
+
COPY --from=build --chown=app:app /app/dist ./dist
|
|
18
|
+
COPY --chown=app:app package.json ./
|
|
19
|
+
USER app
|
|
20
|
+
ENV NODE_ENV=production MCP_TRANSPORT=http PORT=8080 MCP_DATA_DIR=/data
|
|
21
|
+
VOLUME /data
|
|
22
|
+
EXPOSE 8080
|
|
23
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
24
|
+
CMD wget -qO- "http://127.0.0.1:${PORT}/health" >/dev/null || exit 1
|
|
25
|
+
ENTRYPOINT ["node", "dist/server.js"]
|
|
26
|
+
CMD ["serve"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
Your own MCP server, built on [Knowledge MCP](https://github.com/chanthr/internal-mcp) __VERSION__.
|
|
4
|
+
Each file in `src/plugins/` is a plugin that adds tools. The built-in Google Drive, Slack, GitHub and Railway
|
|
5
|
+
plugins turn on when you set their env vars in `.env`.
|
|
6
|
+
|
|
7
|
+
## Run
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm run dev # http://localhost:8080/mcp, reloads on save
|
|
11
|
+
npm run keys -- create --email you@example.com --role admin # prints an API key once
|
|
12
|
+
npm run doctor # which plugins are on, and what each one is missing
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Clients send the key as `Authorization: Bearer <key>`. State (keys, users, plugin data) lives in `./data`.
|
|
16
|
+
|
|
17
|
+
## Add a tool
|
|
18
|
+
|
|
19
|
+
Copy `src/plugins/notes.ts` to `src/plugins/<name>.ts` and edit it:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { definePlugin, defineTool, z } from '@chnxthr/knowledge-mcp';
|
|
23
|
+
|
|
24
|
+
export default definePlugin({
|
|
25
|
+
id: 'weather', // also the permission group
|
|
26
|
+
env: ['WEATHER_API_KEY'], // tools stay hidden until this is set
|
|
27
|
+
tools: [
|
|
28
|
+
defineTool({
|
|
29
|
+
name: 'weather_now',
|
|
30
|
+
description: 'Current weather for a city',
|
|
31
|
+
input: z.object({ city: z.string().describe('City name') }),
|
|
32
|
+
annotations: { readOnlyHint: true },
|
|
33
|
+
run: async ({ city }) => {
|
|
34
|
+
const r = await fetch(`https://api.example.com/weather?q=${encodeURIComponent(city)}`, {
|
|
35
|
+
headers: { Authorization: `Bearer ${process.env.WEATHER_API_KEY}` },
|
|
36
|
+
});
|
|
37
|
+
return r.json(); // string, any JSON value, or a full MCP result
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`run` gets the validated input and a context: `ctx.user` (email, role), `ctx.state` (persistent key-value
|
|
45
|
+
storage for this plugin), `ctx.config` (`plugins.<id>` from `mcp.config.json`) and `ctx.log`.
|
|
46
|
+
Throw `ToolError` for an expected failure. Files starting with `_` are skipped.
|
|
47
|
+
|
|
48
|
+
## Connect a client
|
|
49
|
+
|
|
50
|
+
| Client | How |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| Claude Code (local) | `claude mcp add __NAME__ -- node /absolute/path/__NAME__/dist/server.js serve --stdio` after `npm run build` |
|
|
53
|
+
| Any HTTP client | URL `http://localhost:8080/mcp`, header `Authorization: Bearer <key>` |
|
|
54
|
+
| Claude web and desktop connectors | Deploy behind HTTPS and turn on OAuth ([guide](https://github.com/chanthr/internal-mcp/blob/main/OAUTH_SETUP.md)) |
|
|
55
|
+
|
|
56
|
+
stdio runs as the local admin unless you set `MCP_API_KEY`.
|
|
57
|
+
|
|
58
|
+
## Permissions
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm run users -- add teammate@example.com --role member --tools notes,drive
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Admins see every plugin. Members see every plugin unless `--tools` limits them. Signed-in people without
|
|
65
|
+
a users entry are viewers: they get `ping` and the plugins listed in `access.viewerTools` of `mcp.config.json`.
|
|
66
|
+
|
|
67
|
+
## Deploy
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
docker build -t __NAME__ .
|
|
71
|
+
docker run -d -p 8080:8080 -v __NAME__-data:/data --env-file .env __NAME__
|
|
72
|
+
docker exec <container> node dist/server.js keys create --email you@example.com --role admin
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Keep `/data` on a volume, or set `MCP_STORE=supabase` to keep state in Supabase.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/server.ts serve",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/server.js serve",
|
|
10
|
+
"keys": "tsx src/server.ts keys",
|
|
11
|
+
"users": "tsx src/server.ts users",
|
|
12
|
+
"doctor": "tsx src/server.ts doctor",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@chnxthr/knowledge-mcp": "__CORE_SPEC__"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.10.0",
|
|
20
|
+
"tsx": "^4.19.0",
|
|
21
|
+
"typescript": "^5.6.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22.13.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example plugin — copy this file to add your own tools. Each file in src/plugins is one plugin.
|
|
3
|
+
*
|
|
4
|
+
* The plugin id ("notes") is also its permission group: admins see it, members see it unless their
|
|
5
|
+
* allowed tools exclude it, and viewers see it only if config access.viewerTools lists it.
|
|
6
|
+
*/
|
|
7
|
+
import { definePlugin, defineTool, ToolError, z } from '@chnxthr/knowledge-mcp';
|
|
8
|
+
|
|
9
|
+
type Note = { text: string; by: string; at: string };
|
|
10
|
+
|
|
11
|
+
export default definePlugin({
|
|
12
|
+
id: 'notes',
|
|
13
|
+
description: 'Shared team notes (example plugin)',
|
|
14
|
+
// env: ['MY_API_KEY'], // tools stay hidden until these env vars are set
|
|
15
|
+
instructions: 'Notes: save short facts with notes_add, read them back with notes_list.',
|
|
16
|
+
tools: [
|
|
17
|
+
defineTool({
|
|
18
|
+
name: 'notes_add',
|
|
19
|
+
description: 'Save a short note for the team.',
|
|
20
|
+
input: z.object({
|
|
21
|
+
text: z.string().min(1).max(500).describe('The note'),
|
|
22
|
+
}),
|
|
23
|
+
run: async ({ text }, ctx) => {
|
|
24
|
+
const notes = (await ctx.state.get<Note[]>('all')) ?? [];
|
|
25
|
+
notes.push({ text, by: ctx.user.email ?? 'unknown', at: new Date().toISOString() });
|
|
26
|
+
await ctx.state.set('all', notes.slice(-200));
|
|
27
|
+
return `Saved. ${notes.length} notes in total.`;
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
defineTool({
|
|
31
|
+
name: 'notes_list',
|
|
32
|
+
description: 'List the saved team notes, newest first.',
|
|
33
|
+
input: z.object({
|
|
34
|
+
limit: z.number().int().min(1).max(50).default(10).describe('How many notes'),
|
|
35
|
+
}),
|
|
36
|
+
annotations: { readOnlyHint: true },
|
|
37
|
+
run: async ({ limit }, ctx) => {
|
|
38
|
+
const notes = (await ctx.state.get<Note[]>('all')) ?? [];
|
|
39
|
+
if (!notes.length) throw new ToolError('No notes yet. Add one with notes_add.');
|
|
40
|
+
return notes.slice(-limit).reverse(); // any JSON value is returned as pretty JSON
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your MCP server. Every file in ./plugins is loaded as a plugin; the built-in Drive, Slack, GitHub and
|
|
3
|
+
* Railway plugins turn on when their env vars are set (.env). `npm run doctor` shows what is on.
|
|
4
|
+
*
|
|
5
|
+
* The same file is the command line: serve, serve --stdio, keys, users, doctor, <plugin> <command>.
|
|
6
|
+
*/
|
|
7
|
+
import { run, builtinPlugins, loadPlugins } from '@chnxthr/knowledge-mcp';
|
|
8
|
+
|
|
9
|
+
const mine = await loadPlugins(new URL('./plugins/', import.meta.url));
|
|
10
|
+
|
|
11
|
+
await run({ plugins: [...builtinPlugins, ...mine] });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"]
|
|
16
|
+
}
|