@teknologika/chisel-knowledge-mcp 0.1.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/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/domains/workspace/index.d.ts +3 -0
- package/dist/domains/workspace/index.d.ts.map +1 -0
- package/dist/domains/workspace/index.js +2 -0
- package/dist/domains/workspace/knowledge-index.d.ts +12 -0
- package/dist/domains/workspace/knowledge-index.d.ts.map +1 -0
- package/dist/domains/workspace/knowledge-index.js +151 -0
- package/dist/domains/workspace/workspace.service.d.ts +21 -0
- package/dist/domains/workspace/workspace.service.d.ts.map +1 -0
- package/dist/domains/workspace/workspace.service.js +198 -0
- package/dist/domains/workspace/workspace.types.d.ts +59 -0
- package/dist/domains/workspace/workspace.types.d.ts.map +1 -0
- package/dist/domains/workspace/workspace.types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/infrastructure/mcp/index.d.ts +3 -0
- package/dist/infrastructure/mcp/index.d.ts.map +1 -0
- package/dist/infrastructure/mcp/index.js +2 -0
- package/dist/infrastructure/mcp/mcp-server.d.ts +4 -0
- package/dist/infrastructure/mcp/mcp-server.d.ts.map +1 -0
- package/dist/infrastructure/mcp/mcp-server.js +38 -0
- package/dist/infrastructure/mcp/tool-schemas.d.ts +90 -0
- package/dist/infrastructure/mcp/tool-schemas.d.ts.map +1 -0
- package/dist/infrastructure/mcp/tool-schemas.js +68 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +6 -0
- package/dist/shared/config/config.d.ts +4 -0
- package/dist/shared/config/config.d.ts.map +1 -0
- package/dist/shared/config/config.js +41 -0
- package/dist/shared/config/index.d.ts +2 -0
- package/dist/shared/config/index.d.ts.map +1 -0
- package/dist/shared/config/index.js +1 -0
- package/dist/shared/logging/index.d.ts +2 -0
- package/dist/shared/logging/index.d.ts.map +1 -0
- package/dist/shared/logging/index.js +1 -0
- package/dist/shared/logging/logger.d.ts +3 -0
- package/dist/shared/logging/logger.d.ts.map +1 -0
- package/dist/shared/logging/logger.js +5 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bruce McLeod
|
|
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
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# chisel-knowledge-mcp
|
|
2
|
+
|
|
3
|
+
Standalone MCP server and library for building and managing knowledge workspaces.
|
|
4
|
+
|
|
5
|
+
Canonical behavior documentation lives in [docs/chisel-knowledge-mcp.md](./docs/chisel-knowledge-mcp.md).
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js 22 or newer
|
|
10
|
+
- `npm`
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Library usage
|
|
19
|
+
|
|
20
|
+
Import the workspace service and related types directly from the package root:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { WorkspaceService, KnowledgeIndex } from '@teknologika/chisel-knowledge-mcp';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The MCP server remains available from the `server` subpath and through the published binary.
|
|
27
|
+
|
|
28
|
+
## Build
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Type check
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx tsc --noEmit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Config
|
|
41
|
+
|
|
42
|
+
The server reads workspace configuration from:
|
|
43
|
+
|
|
44
|
+
`~/.chisel/config.json`
|
|
45
|
+
|
|
46
|
+
If the file does not exist, the server starts with zero configured workspaces and logs a warning to stderr. See [config.example.json](./config.example.json) for the expected shape.
|
|
47
|
+
|
|
48
|
+
## Claude Desktop
|
|
49
|
+
|
|
50
|
+
Use this MCP server with Claude Desktop by adding:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"chisel-knowledge": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "@teknologika/chisel-knowledge-mcp"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- Transport is stdio only.
|
|
66
|
+
- Logging goes to stderr so stdout stays reserved for MCP protocol messages.
|
|
67
|
+
- The binary is `chisel-knowledge-mcp`.
|
|
68
|
+
- The package root exports the library surface; `@teknologika/chisel-knowledge-mcp/server` resolves to the MCP server entry point.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const bunBinary = process.env.BUN ?? 'bun';
|
|
6
|
+
const serverPath = resolve(dirname(fileURLToPath(import.meta.url)), 'server.js');
|
|
7
|
+
const result = spawnSync(bunBinary, [serverPath, ...process.argv.slice(2)], {
|
|
8
|
+
stdio: 'inherit',
|
|
9
|
+
});
|
|
10
|
+
if (result.error) {
|
|
11
|
+
const message = result.error instanceof Error ? result.error.message : String(result.error);
|
|
12
|
+
process.stderr.write(`Failed to start Bun CLI launcher (${bunBinary}): ${message}\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
process.exit(typeof result.status === 'number' ? result.status : 1);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/domains/workspace/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SearchResults } from './workspace.types.js';
|
|
2
|
+
export declare class KnowledgeIndex {
|
|
3
|
+
private readonly workspacePath;
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(workspacePath: string);
|
|
6
|
+
indexFile(absPath: string): void;
|
|
7
|
+
removeFile(absPath: string): void;
|
|
8
|
+
search(query: string, limit?: number): SearchResults;
|
|
9
|
+
close(): void;
|
|
10
|
+
private withTransaction;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=knowledge-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-index.d.ts","sourceRoot":"","sources":["../../../src/domains/workspace/knowledge-index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8B1D,qBAAa,cAAc;IAGb,OAAO,CAAC,QAAQ,CAAC,aAAa;IAF1C,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;gBAED,aAAa,EAAE,MAAM;IAOlD,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAwBhC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa;IA6BhD,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,eAAe;CAGxB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
+
const SCHEMA = `
|
|
5
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
6
|
+
id INTEGER PRIMARY KEY,
|
|
7
|
+
path TEXT NOT NULL,
|
|
8
|
+
heading TEXT,
|
|
9
|
+
body TEXT NOT NULL,
|
|
10
|
+
mtime INTEGER NOT NULL
|
|
11
|
+
);
|
|
12
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
|
|
13
|
+
path, heading, body,
|
|
14
|
+
content='chunks', content_rowid='id'
|
|
15
|
+
);
|
|
16
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
17
|
+
INSERT INTO chunks_fts(rowid, path, heading, body)
|
|
18
|
+
VALUES (new.id, new.path, new.heading, new.body);
|
|
19
|
+
END;
|
|
20
|
+
CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
|
|
21
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, path, heading, body)
|
|
22
|
+
VALUES ('delete', old.id, old.path, old.heading, old.body);
|
|
23
|
+
END;
|
|
24
|
+
CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
|
|
25
|
+
INSERT INTO chunks_fts(chunks_fts, rowid, path, heading, body)
|
|
26
|
+
VALUES ('delete', old.id, old.path, old.heading, old.body);
|
|
27
|
+
INSERT INTO chunks_fts(rowid, path, heading, body)
|
|
28
|
+
VALUES (new.id, new.path, new.heading, new.body);
|
|
29
|
+
END;
|
|
30
|
+
`;
|
|
31
|
+
export class KnowledgeIndex {
|
|
32
|
+
workspacePath;
|
|
33
|
+
db;
|
|
34
|
+
constructor(workspacePath) {
|
|
35
|
+
this.workspacePath = workspacePath;
|
|
36
|
+
const dbPath = join(workspacePath, '.knowledge-index.db');
|
|
37
|
+
this.db = new DatabaseSync(dbPath);
|
|
38
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
39
|
+
this.db.exec(SCHEMA);
|
|
40
|
+
}
|
|
41
|
+
indexFile(absPath) {
|
|
42
|
+
const path = relative(this.workspacePath, absPath);
|
|
43
|
+
const mtime = statSync(absPath).mtimeMs;
|
|
44
|
+
const existing = this.db
|
|
45
|
+
.prepare('SELECT mtime FROM chunks WHERE path = ? LIMIT 1')
|
|
46
|
+
.get(path);
|
|
47
|
+
if (existing && existing.mtime === mtime)
|
|
48
|
+
return;
|
|
49
|
+
this.db.prepare('DELETE FROM chunks WHERE path = ?').run(path);
|
|
50
|
+
const raw = readFileSync(absPath, 'utf8');
|
|
51
|
+
const chunks = chunkMarkdown(raw);
|
|
52
|
+
const insert = this.db.prepare('INSERT INTO chunks (path, heading, body, mtime) VALUES (?, ?, ?, ?)');
|
|
53
|
+
this.db.exec('BEGIN');
|
|
54
|
+
try {
|
|
55
|
+
for (const chunk of chunks) {
|
|
56
|
+
insert.run(path, chunk.heading ?? null, chunk.body, mtime);
|
|
57
|
+
}
|
|
58
|
+
this.db.exec('COMMIT');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
this.db.exec('ROLLBACK');
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
removeFile(absPath) {
|
|
66
|
+
const path = relative(this.workspacePath, absPath);
|
|
67
|
+
this.db.prepare('DELETE FROM chunks WHERE path = ?').run(path);
|
|
68
|
+
}
|
|
69
|
+
search(query, limit = 10) {
|
|
70
|
+
const normalized = normalizeFtsQuery(query);
|
|
71
|
+
if (!normalized)
|
|
72
|
+
return { results: [] };
|
|
73
|
+
const rows = this.db
|
|
74
|
+
.prepare(`
|
|
75
|
+
SELECT c.path, c.heading,
|
|
76
|
+
snippet(chunks_fts, 2, '[', ']', '...', 16) AS excerpt
|
|
77
|
+
FROM chunks_fts f
|
|
78
|
+
JOIN chunks c ON c.id = f.rowid
|
|
79
|
+
WHERE chunks_fts MATCH ?
|
|
80
|
+
ORDER BY bm25(chunks_fts)
|
|
81
|
+
LIMIT ?
|
|
82
|
+
`)
|
|
83
|
+
.all(normalized, limit);
|
|
84
|
+
return {
|
|
85
|
+
results: rows.map((row, index) => ({
|
|
86
|
+
file: row.path,
|
|
87
|
+
excerpt: row.heading ? `**${row.heading}**\n${row.excerpt}` : row.excerpt,
|
|
88
|
+
score: 1 - index / Math.max(rows.length, 1),
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
this.db.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function chunkMarkdown(content) {
|
|
97
|
+
const stripped = content.replace(/^---[\s\S]*?---\n?/, '');
|
|
98
|
+
const cleaned = stripped
|
|
99
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
100
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
101
|
+
.trim();
|
|
102
|
+
const lines = cleaned.split('\n');
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let currentHeading = null;
|
|
105
|
+
let currentLines = [];
|
|
106
|
+
const flush = () => {
|
|
107
|
+
const body = currentLines.join('\n').trim();
|
|
108
|
+
if (!body)
|
|
109
|
+
return;
|
|
110
|
+
if (body.length > 1500) {
|
|
111
|
+
const paragraphs = body.split(/\n{2,}/);
|
|
112
|
+
let acc = '';
|
|
113
|
+
for (const para of paragraphs) {
|
|
114
|
+
if (acc.length + para.length > 1500 && acc.length > 0) {
|
|
115
|
+
chunks.push({ heading: currentHeading, body: acc.trim() });
|
|
116
|
+
acc = para;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
acc = acc ? `${acc}\n\n${para}` : para;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (acc.trim())
|
|
123
|
+
chunks.push({ heading: currentHeading, body: acc.trim() });
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
chunks.push({ heading: currentHeading, body });
|
|
127
|
+
}
|
|
128
|
+
currentLines = [];
|
|
129
|
+
};
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
const headingMatch = /^(#{1,3})\s+(.+)/.exec(line);
|
|
132
|
+
if (headingMatch) {
|
|
133
|
+
flush();
|
|
134
|
+
currentHeading = headingMatch[2].trim();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
currentLines.push(line);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
flush();
|
|
141
|
+
return chunks;
|
|
142
|
+
}
|
|
143
|
+
function normalizeFtsQuery(query) {
|
|
144
|
+
return query
|
|
145
|
+
.trim()
|
|
146
|
+
.split(/\s+/)
|
|
147
|
+
.map((token) => token.replace(/[^a-zA-Z0-9_]/g, ''))
|
|
148
|
+
.filter((token) => token.length > 0)
|
|
149
|
+
.map((token) => `${token}*`)
|
|
150
|
+
.join(' ');
|
|
151
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ArchiveResult, ConfigFile, IngestResult, InboxListResult, KnowledgeListResult, ReadResult, SearchResults, WorkspaceConfig, WorkspaceListing, WorkspaceStatus, WriteResult } from './workspace.types.js';
|
|
2
|
+
export declare class WorkspaceService {
|
|
3
|
+
private readonly config;
|
|
4
|
+
constructor(config?: ConfigFile);
|
|
5
|
+
getConfig(): ConfigFile;
|
|
6
|
+
resolve(name: string): WorkspaceConfig;
|
|
7
|
+
listWorkspaces(): WorkspaceListing[];
|
|
8
|
+
status(name: string): WorkspaceStatus;
|
|
9
|
+
ingestText(name: string, content: string, title?: string): IngestResult;
|
|
10
|
+
ingestClipboard(name: string, title?: string): IngestResult;
|
|
11
|
+
ingestUrl(name: string, url: string, title?: string): Promise<IngestResult>;
|
|
12
|
+
search(name: string, query: string, limit?: number): SearchResults;
|
|
13
|
+
read(name: string, pathName: string): ReadResult;
|
|
14
|
+
list(name: string, directory?: string): KnowledgeListResult;
|
|
15
|
+
listInbox(name: string): InboxListResult;
|
|
16
|
+
write(name: string, pathName: string, content: string): WriteResult;
|
|
17
|
+
archive(name: string, file: string): ArchiveResult;
|
|
18
|
+
private collectMarkdownFiles;
|
|
19
|
+
private writeInboxMarkdown;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=workspace.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.service.d.ts","sourceRoot":"","sources":["../../../src/domains/workspace/workspace.service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,eAAe,EAEf,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAI9B,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,GAAE,UAAyB;IAE9D,SAAS,IAAI,UAAU;IAIvB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;IAUtC,cAAc,IAAI,gBAAgB,EAAE;IAOpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;IAkCrC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY;IAMvE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY;IAOrD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA0CjF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa;IAsB9D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU;IAWhD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB;IAe3D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;IAexC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW;IAYnE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa;IA0BlD,OAAO,CAAC,oBAAoB;IAmC5B,OAAO,CAAC,kBAAkB;CAY3B"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, resolve, join, relative } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { KnowledgeIndex } from './knowledge-index.js';
|
|
6
|
+
import { loadConfig } from '../../shared/config/index.js';
|
|
7
|
+
export class WorkspaceService {
|
|
8
|
+
config;
|
|
9
|
+
constructor(config = loadConfig()) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
getConfig() {
|
|
13
|
+
return this.config;
|
|
14
|
+
}
|
|
15
|
+
resolve(name) {
|
|
16
|
+
const workspace = this.config.workspaces.find((entry) => entry.name === name);
|
|
17
|
+
if (!workspace) {
|
|
18
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown workspace: ${name}`);
|
|
19
|
+
}
|
|
20
|
+
return workspace;
|
|
21
|
+
}
|
|
22
|
+
listWorkspaces() {
|
|
23
|
+
return this.config.workspaces.map((workspace) => ({
|
|
24
|
+
...workspace,
|
|
25
|
+
exists: existsSync(workspace.path),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
status(name) {
|
|
29
|
+
const workspace = this.resolve(name);
|
|
30
|
+
const exists = existsSync(workspace.path);
|
|
31
|
+
return {
|
|
32
|
+
...workspace,
|
|
33
|
+
exists,
|
|
34
|
+
inboxCount: 0,
|
|
35
|
+
knowledgeCount: 0,
|
|
36
|
+
lastCompiled: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
ingestText(name, content, title) {
|
|
40
|
+
const workspace = this.resolve(name);
|
|
41
|
+
const inboxPath = join(workspace.path, 'inbox');
|
|
42
|
+
mkdirSync(inboxPath, { recursive: true });
|
|
43
|
+
const filePath = join(inboxPath, `${currentDateStamp()}-${slugify(title)}.md`);
|
|
44
|
+
writeFileSync(filePath, content, 'utf8');
|
|
45
|
+
return {
|
|
46
|
+
file: relative(workspace.path, filePath),
|
|
47
|
+
workspace: workspace.name,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
ingestClipboard(name, title) {
|
|
51
|
+
const clipboard = execSync('pbpaste', { encoding: 'utf8' });
|
|
52
|
+
return this.ingestText(name, clipboard, title);
|
|
53
|
+
}
|
|
54
|
+
async ingestUrl(name, url, title) {
|
|
55
|
+
const jinaUrl = `https://r.jina.ai/${url}`;
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await fetch(jinaUrl, {
|
|
59
|
+
headers: { Accept: 'text/markdown' },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
throw new McpError(ErrorCode.InternalError, `Failed to fetch URL: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new McpError(ErrorCode.InternalError, `Jina Reader returned ${response.status} for ${url}`);
|
|
67
|
+
}
|
|
68
|
+
const content = await response.text();
|
|
69
|
+
const resolvedTitle = title ?? extractJinaTitle(content) ?? url;
|
|
70
|
+
return this.ingestText(name, content, resolvedTitle);
|
|
71
|
+
}
|
|
72
|
+
search(name, query, limit = 10) {
|
|
73
|
+
const workspace = this.resolve(name);
|
|
74
|
+
const knowledgeRoot = join(workspace.path, 'knowledge');
|
|
75
|
+
if (!existsSync(knowledgeRoot)) {
|
|
76
|
+
return { results: [] };
|
|
77
|
+
}
|
|
78
|
+
const index = new KnowledgeIndex(workspace.path);
|
|
79
|
+
try {
|
|
80
|
+
const files = this.collectMarkdownFiles(workspace.path, knowledgeRoot);
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
index.indexFile(join(workspace.path, file.path));
|
|
83
|
+
}
|
|
84
|
+
return index.search(query, limit);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
index.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
read(name, pathName) {
|
|
91
|
+
const workspace = this.resolve(name);
|
|
92
|
+
const filePath = resolve(workspace.path, pathName);
|
|
93
|
+
const content = readFileSync(filePath, 'utf8');
|
|
94
|
+
return {
|
|
95
|
+
content,
|
|
96
|
+
file: relative(workspace.path, filePath),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
list(name, directory) {
|
|
100
|
+
const workspace = this.resolve(name);
|
|
101
|
+
const knowledgeRoot = directory ? resolve(workspace.path, directory) : join(workspace.path, 'knowledge');
|
|
102
|
+
if (!existsSync(knowledgeRoot)) {
|
|
103
|
+
return { files: [] };
|
|
104
|
+
}
|
|
105
|
+
const files = this.collectMarkdownFiles(workspace.path, knowledgeRoot);
|
|
106
|
+
return {
|
|
107
|
+
files,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
listInbox(name) {
|
|
111
|
+
const workspace = this.resolve(name);
|
|
112
|
+
const inboxRoot = join(workspace.path, 'inbox');
|
|
113
|
+
if (!existsSync(inboxRoot)) {
|
|
114
|
+
return { files: [] };
|
|
115
|
+
}
|
|
116
|
+
const files = this.collectMarkdownFiles(workspace.path, inboxRoot, ['archived']);
|
|
117
|
+
return {
|
|
118
|
+
files,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
write(name, pathName, content) {
|
|
122
|
+
const workspace = this.resolve(name);
|
|
123
|
+
const target = join(workspace.path, 'knowledge', pathName);
|
|
124
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
125
|
+
writeFileSync(target, content, 'utf8');
|
|
126
|
+
return {
|
|
127
|
+
file: relative(workspace.path, target),
|
|
128
|
+
workspace: name,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
archive(name, file) {
|
|
132
|
+
const workspace = this.resolve(name);
|
|
133
|
+
const source = join(workspace.path, file);
|
|
134
|
+
if (!existsSync(source)) {
|
|
135
|
+
throw new McpError(ErrorCode.InvalidParams, `File not found: ${file}`);
|
|
136
|
+
}
|
|
137
|
+
const archivedDir = join(workspace.path, 'inbox', 'archived');
|
|
138
|
+
mkdirSync(archivedDir, { recursive: true });
|
|
139
|
+
let destination = join(archivedDir, basename(source));
|
|
140
|
+
if (existsSync(destination)) {
|
|
141
|
+
destination = join(archivedDir, `${Date.now()}-${basename(source)}`);
|
|
142
|
+
}
|
|
143
|
+
renameSync(source, destination);
|
|
144
|
+
return {
|
|
145
|
+
original: relative(workspace.path, source),
|
|
146
|
+
archived: relative(workspace.path, destination),
|
|
147
|
+
workspace: name,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
collectMarkdownFiles(workspaceRoot, directory, excludedDirectories = []) {
|
|
151
|
+
const entries = readdirSync(directory, { withFileTypes: true });
|
|
152
|
+
const files = [];
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
const entryPath = join(directory, entry.name);
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
if (excludedDirectories.includes(entry.name)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
files.push(...this.collectMarkdownFiles(workspaceRoot, entryPath, excludedDirectories));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (!entry.name.endsWith('.md')) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const stats = statSync(entryPath);
|
|
166
|
+
files.push({
|
|
167
|
+
path: relative(workspaceRoot, entryPath),
|
|
168
|
+
size: stats.size,
|
|
169
|
+
modified: stats.mtime.toISOString(),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function currentDateStamp(date = new Date()) {
|
|
176
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
177
|
+
year: 'numeric',
|
|
178
|
+
month: '2-digit',
|
|
179
|
+
day: '2-digit',
|
|
180
|
+
}).formatToParts(date);
|
|
181
|
+
const byType = new Map(parts.map((part) => [part.type, part.value]));
|
|
182
|
+
return `${byType.get('year')}-${byType.get('month')}-${byType.get('day')}`;
|
|
183
|
+
}
|
|
184
|
+
function slugify(title) {
|
|
185
|
+
const raw = (title ?? 'untitled').toLowerCase();
|
|
186
|
+
const slug = raw
|
|
187
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
188
|
+
.trim()
|
|
189
|
+
.replace(/\s+/g, '-')
|
|
190
|
+
.replace(/-+/g, '-')
|
|
191
|
+
.replace(/^-+|-+$/g, '')
|
|
192
|
+
.slice(0, 50);
|
|
193
|
+
return slug || 'untitled';
|
|
194
|
+
}
|
|
195
|
+
function extractJinaTitle(content) {
|
|
196
|
+
const match = /^Title:\s*(.+)$/m.exec(content);
|
|
197
|
+
return match ? match[1].trim() : null;
|
|
198
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type WorkspaceConfig = {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
};
|
|
5
|
+
export type ConfigFile = {
|
|
6
|
+
workspaces: WorkspaceConfig[];
|
|
7
|
+
};
|
|
8
|
+
export type WorkspaceListing = WorkspaceConfig & {
|
|
9
|
+
exists: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type WorkspaceStatus = WorkspaceListing & {
|
|
12
|
+
inboxCount: number;
|
|
13
|
+
knowledgeCount: number;
|
|
14
|
+
lastCompiled: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type IngestResult = {
|
|
17
|
+
file: string;
|
|
18
|
+
workspace: string;
|
|
19
|
+
imagesLocalised?: number;
|
|
20
|
+
imagesFailed?: number;
|
|
21
|
+
};
|
|
22
|
+
export type InboxFile = {
|
|
23
|
+
path: string;
|
|
24
|
+
size: number;
|
|
25
|
+
modified: string;
|
|
26
|
+
};
|
|
27
|
+
export type InboxListResult = {
|
|
28
|
+
files: InboxFile[];
|
|
29
|
+
};
|
|
30
|
+
export type ReadResult = {
|
|
31
|
+
content: string;
|
|
32
|
+
file: string;
|
|
33
|
+
};
|
|
34
|
+
export type WriteResult = {
|
|
35
|
+
file: string;
|
|
36
|
+
workspace: string;
|
|
37
|
+
};
|
|
38
|
+
export type ArchiveResult = {
|
|
39
|
+
original: string;
|
|
40
|
+
archived: string;
|
|
41
|
+
workspace: string;
|
|
42
|
+
};
|
|
43
|
+
export type KnowledgeFile = {
|
|
44
|
+
path: string;
|
|
45
|
+
size: number;
|
|
46
|
+
modified: string;
|
|
47
|
+
};
|
|
48
|
+
export type KnowledgeListResult = {
|
|
49
|
+
files: KnowledgeFile[];
|
|
50
|
+
};
|
|
51
|
+
export type SearchResult = {
|
|
52
|
+
file: string;
|
|
53
|
+
excerpt: string;
|
|
54
|
+
score: number;
|
|
55
|
+
};
|
|
56
|
+
export type SearchResults = {
|
|
57
|
+
results: SearchResult[];
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=workspace.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.types.d.ts","sourceRoot":"","sources":["../../../src/domains/workspace/workspace.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG;IAC/C,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { WorkspaceService } from './domains/workspace/workspace.service.js';
|
|
2
|
+
export { KnowledgeIndex } from './domains/workspace/knowledge-index.js';
|
|
3
|
+
export type { WorkspaceConfig, ConfigFile, WorkspaceListing, WorkspaceStatus, IngestResult, InboxFile, InboxListResult, ReadResult, WriteResult, ArchiveResult, KnowledgeFile, KnowledgeListResult, SearchResult, SearchResults, } from './domains/workspace/workspace.types.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,YAAY,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,SAAS,EACT,eAAe,EACf,UAAU,EACV,WAAW,EACX,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,aAAa,GACd,MAAM,wCAAwC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Library entry point — exports the service layer for direct use in Chisel
|
|
2
|
+
// and other consumers without requiring the MCP protocol.
|
|
3
|
+
export { WorkspaceService } from './domains/workspace/workspace.service.js';
|
|
4
|
+
export { KnowledgeIndex } from './domains/workspace/knowledge-index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/mcp/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/mcp/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4BpE,wBAAgB,eAAe,IAAI,SAAS,CAqF3C;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAIpD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { WorkspaceService } from '../../domains/workspace/index.js';
|
|
4
|
+
import { KnowledgeArchiveSchema, KnowledgeIngestClipboardSchema, KnowledgeIngestTextSchema, KnowledgeIngestUrlSchema, KnowledgeListInboxSchema, KnowledgeListSchema, KnowledgeListWorkspacesSchema, KnowledgeReadSchema, KnowledgeSearchSchema, KnowledgeWorkspaceStatusSchema, KnowledgeWriteSchema, } from './tool-schemas.js';
|
|
5
|
+
function textContent(value) {
|
|
6
|
+
return {
|
|
7
|
+
content: [
|
|
8
|
+
{
|
|
9
|
+
type: 'text',
|
|
10
|
+
text: JSON.stringify(value, null, 2),
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function createMcpServer() {
|
|
16
|
+
const service = new WorkspaceService();
|
|
17
|
+
const server = new McpServer({
|
|
18
|
+
name: 'chisel-knowledge-mcp',
|
|
19
|
+
version: '0.1.0',
|
|
20
|
+
});
|
|
21
|
+
server.tool('knowledge_list_workspaces', 'List configured workspaces.', KnowledgeListWorkspacesSchema, async () => textContent(service.listWorkspaces()));
|
|
22
|
+
server.tool('knowledge_workspace_status', 'Inspect a workspace.', KnowledgeWorkspaceStatusSchema, async ({ workspace }) => textContent(service.status(workspace)));
|
|
23
|
+
server.tool('knowledge_ingest_text', 'Write raw text into a workspace inbox.', KnowledgeIngestTextSchema, async ({ workspace, content, title }) => textContent(service.ingestText(workspace, content, title)));
|
|
24
|
+
server.tool('knowledge_ingest_clipboard', 'Read clipboard text and write it into a workspace inbox.', KnowledgeIngestClipboardSchema, async ({ workspace, title }) => textContent(service.ingestClipboard(workspace, title)));
|
|
25
|
+
server.tool('knowledge_ingest_url', 'Ingest a URL into a workspace inbox.', KnowledgeIngestUrlSchema, async ({ workspace, url, title }) => textContent(await service.ingestUrl(workspace, url, title)));
|
|
26
|
+
server.tool('knowledge_search', 'Search workspace knowledge.', KnowledgeSearchSchema, async ({ workspace, query, limit }) => textContent(service.search(workspace, query, limit)));
|
|
27
|
+
server.tool('knowledge_list_inbox', 'List uncompiled files in a workspace inbox.', KnowledgeListInboxSchema, async ({ workspace }) => textContent(service.listInbox(workspace)));
|
|
28
|
+
server.tool('knowledge_write', 'Write a compiled article into the workspace knowledge directory.', KnowledgeWriteSchema, async ({ workspace, path, content }) => textContent(service.write(workspace, path, content)));
|
|
29
|
+
server.tool('knowledge_archive', 'Move a processed inbox file to inbox/archived/.', KnowledgeArchiveSchema, async ({ workspace, file }) => textContent(service.archive(workspace, file)));
|
|
30
|
+
server.tool('knowledge_read', 'Read a knowledge file from a workspace.', KnowledgeReadSchema, async ({ workspace, path }) => textContent(service.read(workspace, path)));
|
|
31
|
+
server.tool('knowledge_list', 'List markdown files in a workspace knowledge directory.', KnowledgeListSchema, async ({ workspace, directory }) => textContent(service.list(workspace, directory)));
|
|
32
|
+
return server;
|
|
33
|
+
}
|
|
34
|
+
export async function startMcpServer() {
|
|
35
|
+
const server = createMcpServer();
|
|
36
|
+
const transport = new StdioServerTransport();
|
|
37
|
+
await server.connect(transport);
|
|
38
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const KnowledgeListWorkspacesSchema: {};
|
|
3
|
+
export declare const KnowledgeWorkspaceStatusSchema: {
|
|
4
|
+
workspace: z.ZodString;
|
|
5
|
+
};
|
|
6
|
+
export declare const KnowledgeIngestTextSchema: {
|
|
7
|
+
workspace: z.ZodString;
|
|
8
|
+
content: z.ZodString;
|
|
9
|
+
title: z.ZodOptional<z.ZodString>;
|
|
10
|
+
};
|
|
11
|
+
export declare const KnowledgeIngestClipboardSchema: {
|
|
12
|
+
workspace: z.ZodString;
|
|
13
|
+
title: z.ZodOptional<z.ZodString>;
|
|
14
|
+
};
|
|
15
|
+
export declare const KnowledgeIngestUrlSchema: {
|
|
16
|
+
workspace: z.ZodString;
|
|
17
|
+
url: z.ZodString;
|
|
18
|
+
title: z.ZodOptional<z.ZodString>;
|
|
19
|
+
};
|
|
20
|
+
export declare const KnowledgeSearchSchema: {
|
|
21
|
+
workspace: z.ZodString;
|
|
22
|
+
query: z.ZodString;
|
|
23
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
};
|
|
25
|
+
export declare const KnowledgeListInboxSchema: {
|
|
26
|
+
workspace: z.ZodString;
|
|
27
|
+
};
|
|
28
|
+
export declare const KnowledgeWriteSchema: {
|
|
29
|
+
workspace: z.ZodString;
|
|
30
|
+
path: z.ZodString;
|
|
31
|
+
content: z.ZodString;
|
|
32
|
+
};
|
|
33
|
+
export declare const KnowledgeArchiveSchema: {
|
|
34
|
+
workspace: z.ZodString;
|
|
35
|
+
file: z.ZodString;
|
|
36
|
+
};
|
|
37
|
+
export declare const KnowledgeReadSchema: {
|
|
38
|
+
workspace: z.ZodString;
|
|
39
|
+
path: z.ZodString;
|
|
40
|
+
};
|
|
41
|
+
export declare const KnowledgeListSchema: {
|
|
42
|
+
workspace: z.ZodString;
|
|
43
|
+
directory: z.ZodOptional<z.ZodString>;
|
|
44
|
+
};
|
|
45
|
+
export declare const ToolSchemas: {
|
|
46
|
+
readonly knowledge_list_workspaces: {};
|
|
47
|
+
readonly knowledge_workspace_status: {
|
|
48
|
+
workspace: z.ZodString;
|
|
49
|
+
};
|
|
50
|
+
readonly knowledge_ingest_text: {
|
|
51
|
+
workspace: z.ZodString;
|
|
52
|
+
content: z.ZodString;
|
|
53
|
+
title: z.ZodOptional<z.ZodString>;
|
|
54
|
+
};
|
|
55
|
+
readonly knowledge_ingest_clipboard: {
|
|
56
|
+
workspace: z.ZodString;
|
|
57
|
+
title: z.ZodOptional<z.ZodString>;
|
|
58
|
+
};
|
|
59
|
+
readonly knowledge_ingest_url: {
|
|
60
|
+
workspace: z.ZodString;
|
|
61
|
+
url: z.ZodString;
|
|
62
|
+
title: z.ZodOptional<z.ZodString>;
|
|
63
|
+
};
|
|
64
|
+
readonly knowledge_search: {
|
|
65
|
+
workspace: z.ZodString;
|
|
66
|
+
query: z.ZodString;
|
|
67
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
};
|
|
69
|
+
readonly knowledge_list_inbox: {
|
|
70
|
+
workspace: z.ZodString;
|
|
71
|
+
};
|
|
72
|
+
readonly knowledge_write: {
|
|
73
|
+
workspace: z.ZodString;
|
|
74
|
+
path: z.ZodString;
|
|
75
|
+
content: z.ZodString;
|
|
76
|
+
};
|
|
77
|
+
readonly knowledge_archive: {
|
|
78
|
+
workspace: z.ZodString;
|
|
79
|
+
file: z.ZodString;
|
|
80
|
+
};
|
|
81
|
+
readonly knowledge_read: {
|
|
82
|
+
workspace: z.ZodString;
|
|
83
|
+
path: z.ZodString;
|
|
84
|
+
};
|
|
85
|
+
readonly knowledge_list: {
|
|
86
|
+
workspace: z.ZodString;
|
|
87
|
+
directory: z.ZodOptional<z.ZodString>;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=tool-schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-schemas.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/mcp/tool-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA8CxB,eAAO,MAAM,6BAA6B,IAA2C,CAAC;AACtF,eAAO,MAAM,8BAA8B;;CAA4C,CAAC;AACxF,eAAO,MAAM,yBAAyB;;;;CAAuC,CAAC;AAC9E,eAAO,MAAM,8BAA8B;;;CAA4C,CAAC;AACxF,eAAO,MAAM,wBAAwB;;;;CAAsC,CAAC;AAC5E,eAAO,MAAM,qBAAqB;;;;CAAmC,CAAC;AACtE,eAAO,MAAM,wBAAwB;;CAAsC,CAAC;AAC5E,eAAO,MAAM,oBAAoB;;;;CAAkC,CAAC;AACpE,eAAO,MAAM,sBAAsB;;;CAAoC,CAAC;AACxE,eAAO,MAAM,mBAAmB;;;CAAiC,CAAC;AAClE,eAAO,MAAM,mBAAmB;;;CAAiC,CAAC;AAElE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYd,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const KnowledgeListWorkspacesInputSchema = z.object({});
|
|
3
|
+
const KnowledgeWorkspaceStatusInputSchema = z.object({
|
|
4
|
+
workspace: z.string(),
|
|
5
|
+
});
|
|
6
|
+
const KnowledgeIngestTextInputSchema = z.object({
|
|
7
|
+
workspace: z.string(),
|
|
8
|
+
content: z.string(),
|
|
9
|
+
title: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
const KnowledgeIngestClipboardInputSchema = z.object({
|
|
12
|
+
workspace: z.string(),
|
|
13
|
+
title: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
const KnowledgeIngestUrlInputSchema = z.object({
|
|
16
|
+
workspace: z.string(),
|
|
17
|
+
url: z.string(),
|
|
18
|
+
title: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
const KnowledgeSearchInputSchema = z.object({
|
|
21
|
+
workspace: z.string(),
|
|
22
|
+
query: z.string(),
|
|
23
|
+
limit: z.number().int().positive().optional(),
|
|
24
|
+
});
|
|
25
|
+
const KnowledgeListInboxInputSchema = z.object({
|
|
26
|
+
workspace: z.string(),
|
|
27
|
+
});
|
|
28
|
+
const KnowledgeWriteInputSchema = z.object({
|
|
29
|
+
workspace: z.string(),
|
|
30
|
+
path: z.string(),
|
|
31
|
+
content: z.string(),
|
|
32
|
+
});
|
|
33
|
+
const KnowledgeArchiveInputSchema = z.object({
|
|
34
|
+
workspace: z.string(),
|
|
35
|
+
file: z.string(),
|
|
36
|
+
});
|
|
37
|
+
const KnowledgeReadInputSchema = z.object({
|
|
38
|
+
workspace: z.string(),
|
|
39
|
+
path: z.string(),
|
|
40
|
+
});
|
|
41
|
+
const KnowledgeListInputSchema = z.object({
|
|
42
|
+
workspace: z.string(),
|
|
43
|
+
directory: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
export const KnowledgeListWorkspacesSchema = KnowledgeListWorkspacesInputSchema.shape;
|
|
46
|
+
export const KnowledgeWorkspaceStatusSchema = KnowledgeWorkspaceStatusInputSchema.shape;
|
|
47
|
+
export const KnowledgeIngestTextSchema = KnowledgeIngestTextInputSchema.shape;
|
|
48
|
+
export const KnowledgeIngestClipboardSchema = KnowledgeIngestClipboardInputSchema.shape;
|
|
49
|
+
export const KnowledgeIngestUrlSchema = KnowledgeIngestUrlInputSchema.shape;
|
|
50
|
+
export const KnowledgeSearchSchema = KnowledgeSearchInputSchema.shape;
|
|
51
|
+
export const KnowledgeListInboxSchema = KnowledgeListInboxInputSchema.shape;
|
|
52
|
+
export const KnowledgeWriteSchema = KnowledgeWriteInputSchema.shape;
|
|
53
|
+
export const KnowledgeArchiveSchema = KnowledgeArchiveInputSchema.shape;
|
|
54
|
+
export const KnowledgeReadSchema = KnowledgeReadInputSchema.shape;
|
|
55
|
+
export const KnowledgeListSchema = KnowledgeListInputSchema.shape;
|
|
56
|
+
export const ToolSchemas = {
|
|
57
|
+
knowledge_list_workspaces: KnowledgeListWorkspacesSchema,
|
|
58
|
+
knowledge_workspace_status: KnowledgeWorkspaceStatusSchema,
|
|
59
|
+
knowledge_ingest_text: KnowledgeIngestTextSchema,
|
|
60
|
+
knowledge_ingest_clipboard: KnowledgeIngestClipboardSchema,
|
|
61
|
+
knowledge_ingest_url: KnowledgeIngestUrlSchema,
|
|
62
|
+
knowledge_search: KnowledgeSearchSchema,
|
|
63
|
+
knowledge_list_inbox: KnowledgeListInboxSchema,
|
|
64
|
+
knowledge_write: KnowledgeWriteSchema,
|
|
65
|
+
knowledge_archive: KnowledgeArchiveSchema,
|
|
66
|
+
knowledge_read: KnowledgeReadSchema,
|
|
67
|
+
knowledge_list: KnowledgeListSchema,
|
|
68
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/shared/config/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,4CAA4C,CAAC;AA2B9F,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAgB,UAAU,CAAC,UAAU,GAAE,MAA4B,GAAG,UAAU,CAa/E"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { logger } from '../logging/index.js';
|
|
6
|
+
const WorkspaceConfigSchema = z.object({
|
|
7
|
+
name: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Workspace name must be kebab-case'),
|
|
8
|
+
path: z.string().refine((value) => isAbsolute(value), 'Workspace path must be absolute'),
|
|
9
|
+
});
|
|
10
|
+
const ConfigSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
workspaces: z.array(WorkspaceConfigSchema).default([]),
|
|
13
|
+
})
|
|
14
|
+
.superRefine((config, context) => {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
for (const workspace of config.workspaces) {
|
|
17
|
+
if (seen.has(workspace.name)) {
|
|
18
|
+
context.addIssue({
|
|
19
|
+
code: z.ZodIssueCode.custom,
|
|
20
|
+
message: `Duplicate workspace name: ${workspace.name}`,
|
|
21
|
+
path: ['workspaces'],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
seen.add(workspace.name);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
export function defaultConfigPath() {
|
|
28
|
+
return join(homedir(), '.chisel', 'config.json');
|
|
29
|
+
}
|
|
30
|
+
export function loadConfig(configPath = defaultConfigPath()) {
|
|
31
|
+
if (!existsSync(configPath)) {
|
|
32
|
+
logger.warn({ configPath }, 'Config file not found; starting with zero workspaces');
|
|
33
|
+
return { workspaces: [] };
|
|
34
|
+
}
|
|
35
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
const result = ConfigSchema.parse(parsed);
|
|
38
|
+
return {
|
|
39
|
+
workspaces: result.workspaces,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/config/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/logging/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './logger.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/shared/logging/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,eAAO,MAAM,MAAM,6BAMlB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teknologika/chisel-knowledge-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Standalone MCP server and library for building and managing knowledge workspaces.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./server": "./dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"chisel-knowledge-mcp": "dist/server.js"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/teknologika/chisel-knowledge-mcp.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Teknologika",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
31
|
+
"test": "vitest"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
35
|
+
"pino": "^9.9.0",
|
|
36
|
+
"zod": "^3.25.76"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.15.29",
|
|
40
|
+
"typescript": "^5.8.3",
|
|
41
|
+
"vitest": "^3.2.4"
|
|
42
|
+
}
|
|
43
|
+
}
|