@unson/brainbase-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/dist/server.js ADDED
@@ -0,0 +1,185 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { z } from 'zod';
5
+ import { resolveDataDir } from './paths.js';
6
+ import { auditPersonalOsDirectory } from './ontology-ssot.js';
7
+ import { getOntologyImpact, inferPersonalOs, portableOntology, resolveOntologyVersion } from './ontology.js';
8
+ import { loadPersonalOs } from './ssot.js';
9
+ import { getContext, listEntities, onboardingStatus, searchAll, searchPersonalKg } from './tools.js';
10
+ const argsSchema = z.object({
11
+ dataDir: z.string().optional(),
12
+ query: z.string().optional(),
13
+ limit: z.number().int().positive().max(50).optional(),
14
+ type: z.enum(['person', 'org', 'project', 'relationship', 'decision']).optional(),
15
+ fromVersion: z.string().optional(),
16
+ ontologyVersion: z.string().optional(),
17
+ asOf: z.string().datetime({ offset: true }).optional()
18
+ });
19
+ export const toolDefinitions = [
20
+ {
21
+ name: 'get_context',
22
+ description: 'Return initial AI context from local Graph and Personal KG canonical files.',
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ dataDir: { type: 'string' }
27
+ }
28
+ }
29
+ },
30
+ {
31
+ name: 'list_entities',
32
+ description: 'List person, org, project, relationship, and decision entities from local SSOT.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ dataDir: { type: 'string' },
37
+ type: { enum: ['person', 'org', 'project', 'relationship', 'decision'] }
38
+ }
39
+ }
40
+ },
41
+ {
42
+ name: 'search',
43
+ description: 'Search canonical local Graph, Personal KG, relationships, and decisions.',
44
+ inputSchema: {
45
+ type: 'object',
46
+ required: ['query'],
47
+ properties: {
48
+ dataDir: { type: 'string' },
49
+ query: { type: 'string' },
50
+ limit: { type: 'number' }
51
+ }
52
+ }
53
+ },
54
+ {
55
+ name: 'search_personal_kg',
56
+ description: 'Search owner-local Personal KG only.',
57
+ inputSchema: {
58
+ type: 'object',
59
+ required: ['query'],
60
+ properties: {
61
+ dataDir: { type: 'string' },
62
+ query: { type: 'string' },
63
+ limit: { type: 'number' }
64
+ }
65
+ }
66
+ },
67
+ {
68
+ name: 'onboarding_status',
69
+ description: 'Report seeded areas, first value demo readiness, missing setup, and local connection status.',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ dataDir: { type: 'string' }
74
+ }
75
+ }
76
+ },
77
+ {
78
+ name: 'get_ontology',
79
+ description: 'Return the bundled immutable Brainbase Portable Ontology Kernel release.',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {}
83
+ }
84
+ },
85
+ {
86
+ name: 'audit_ontology',
87
+ description: 'Audit local canonical Personal OS files using the active or recorded historical ontology semantics.',
88
+ inputSchema: {
89
+ type: 'object',
90
+ properties: {
91
+ dataDir: { type: 'string' },
92
+ ontologyVersion: { enum: ['0.0.0', '1.0.0'] }
93
+ }
94
+ }
95
+ },
96
+ {
97
+ name: 'infer_decisions',
98
+ description: 'Derive active, superseded, and conflicting decisions using explicit ontology rules.',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ dataDir: { type: 'string' },
103
+ asOf: { type: 'string', format: 'date-time' },
104
+ ontologyVersion: { enum: ['0.0.0', '1.0.0'] }
105
+ }
106
+ }
107
+ },
108
+ {
109
+ name: 'ontology_impact',
110
+ description: 'Describe compatibility, migration, and rollback from an ontology version to the active release.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {
114
+ fromVersion: { type: 'string' }
115
+ }
116
+ }
117
+ }
118
+ ];
119
+ export async function callBrainbaseTool(name, rawArgs = {}) {
120
+ const args = argsSchema.parse(rawArgs ?? {});
121
+ const dataDir = resolveDataDir(args.dataDir);
122
+ switch (name) {
123
+ case 'get_ontology':
124
+ return portableOntology;
125
+ case 'audit_ontology':
126
+ return auditPersonalOsDirectory(dataDir, { ontologyVersion: resolveOntologyVersion(args.ontologyVersion) });
127
+ case 'ontology_impact':
128
+ return getOntologyImpact(args.fromVersion);
129
+ }
130
+ const os = await loadPersonalOs(dataDir);
131
+ switch (name) {
132
+ case 'get_context':
133
+ return getContext(os);
134
+ case 'list_entities':
135
+ return listEntities(os, args.type);
136
+ case 'search':
137
+ if (!args.query) {
138
+ throw new Error('search requires query');
139
+ }
140
+ return { results: searchAll(os, args.query, args.limit) };
141
+ case 'search_personal_kg':
142
+ if (!args.query) {
143
+ throw new Error('search_personal_kg requires query');
144
+ }
145
+ return { results: searchPersonalKg(os, args.query, args.limit) };
146
+ case 'onboarding_status':
147
+ return onboardingStatus(os);
148
+ case 'infer_decisions':
149
+ return inferPersonalOs(os, {
150
+ asOf: args.asOf,
151
+ ontologyVersion: resolveOntologyVersion(args.ontologyVersion)
152
+ });
153
+ default:
154
+ throw new Error(`Unknown tool: ${name}`);
155
+ }
156
+ }
157
+ export function createServer() {
158
+ const server = new Server({
159
+ name: 'brainbase-mcp',
160
+ version: '0.1.0'
161
+ }, {
162
+ capabilities: {
163
+ tools: {}
164
+ }
165
+ });
166
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
167
+ tools: [...toolDefinitions]
168
+ }));
169
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
170
+ const result = await callBrainbaseTool(request.params.name, request.params.arguments);
171
+ return {
172
+ content: [
173
+ {
174
+ type: 'text',
175
+ text: JSON.stringify(result, null, 2)
176
+ }
177
+ ]
178
+ };
179
+ });
180
+ return server;
181
+ }
182
+ export async function runMcpServer() {
183
+ const server = createServer();
184
+ await server.connect(new StdioServerTransport());
185
+ }
@@ -0,0 +1,30 @@
1
+ export type SkillTarget = 'codex' | 'claude' | 'portable';
2
+ export type BrainbaseSkillId = 'brainbase-personal-onboarding' | 'brainbase-source-import' | 'brainbase-candidate-review' | 'brainbase-daily-routines';
3
+ export interface BrainbaseSkillDefinition {
4
+ id: BrainbaseSkillId;
5
+ title: string;
6
+ description: string;
7
+ body: string;
8
+ }
9
+ export interface SkillFile {
10
+ id: BrainbaseSkillId;
11
+ description: string;
12
+ relativePath: string;
13
+ recommendedPath: string;
14
+ content: string;
15
+ }
16
+ export interface SkillBundle {
17
+ target: SkillTarget;
18
+ goal: string;
19
+ canonicalWrites: false;
20
+ liveConfigWrites: false;
21
+ skills: SkillFile[];
22
+ safetyRules: string[];
23
+ nextSteps: string[];
24
+ }
25
+ export declare const ALL_BRAINBASE_SKILLS: BrainbaseSkillId[];
26
+ export declare function parseSkillTarget(value: string | undefined): SkillTarget;
27
+ export declare function parseSkillIds(value: string | undefined): BrainbaseSkillId[];
28
+ export declare function buildSkillBundle(target: SkillTarget, ids?: BrainbaseSkillId[]): SkillBundle;
29
+ export declare function renderSkillsMarkdown(bundle: SkillBundle, outDir?: string): string;
30
+ export declare function assertPublicSafeSkillBundle(bundle: SkillBundle): void;
package/dist/skills.js ADDED
@@ -0,0 +1,236 @@
1
+ export const ALL_BRAINBASE_SKILLS = [
2
+ 'brainbase-personal-onboarding',
3
+ 'brainbase-source-import',
4
+ 'brainbase-candidate-review',
5
+ 'brainbase-daily-routines'
6
+ ];
7
+ const TARGET_BASE_PATHS = {
8
+ codex: '~/.agents/skills',
9
+ claude: '.claude/skills',
10
+ portable: 'skills'
11
+ };
12
+ const SECRET_RULE = 'OAuthトークン、パスワード、APIキー、リフレッシュトークンをチャットに貼るようユーザーに依頼しない。';
13
+ const SKILL_DEFINITIONS = {
14
+ 'brainbase-personal-onboarding': {
15
+ id: 'brainbase-personal-onboarding',
16
+ title: 'Brainbase個人オンボーディング',
17
+ description: '公開版Brainbaseの個人オンボーディング面談とローカルMCP設定を、安全に進める。',
18
+ body: [
19
+ '## 目的',
20
+ '',
21
+ 'ユーザーがCodex、Claude Code、または他のコーディングエージェントからBrainbaseを使い始める時に使う。',
22
+ '',
23
+ '## 手順',
24
+ '',
25
+ '1. 最初に `brainbase onboard:agent` を実行し、メール、カレンダー、ドライブ/ドキュメント、タスク、権限、承認についてエージェントに確認させる。',
26
+ '2. `brainbase onboard:init` でローカルPersonal OSディレクトリを作成する。',
27
+ '3. ソースデータを集める前に、ユーザーの回答を使って `brainbase onboard:diagnose-sources` を実行する。',
28
+ '4. `brainbase onboard:projects` で、現在のプロジェクト名、目的、状態、役割、関係者、許可されたソースをレビュー可能な形にする。',
29
+ '5. `brainbase onboard:candidates --write`、またはsource import/extractの流れでレビュー候補を作る。',
30
+ '6. ユーザーが明示的に承認した事実だけを `brainbase onboard:seed`、`brainbase onboard:projects --write`、または `brainbase onboard:apply --write` でcanonical SSOTへ昇格する。',
31
+ '7. `brainbase onboard:install --target codex|claude|codecode --dry-run` でMCP設定スニペットを生成し、ユーザーに確認してもらう。',
32
+ '',
33
+ '## 安全ルール',
34
+ '',
35
+ `- ${SECRET_RULE}`,
36
+ '- Brainbaseはローカルファーストで扱う。ユーザーが別のローカルパスを明示しない限り、ローカルMCPサーバーと `~/.brainbase/personal-os/` を使う。',
37
+ '- メール、カレンダー、ドライブ、タスク、メモの生データは `sources/` 配下の二次材料として扱う。',
38
+ '- canonical contextは、ユーザー確認後の `graph.json`、`relationships.json`、`personal-kg.jsonl`、`decisions.jsonl` だけから作る。'
39
+ ].join('\n')
40
+ },
41
+ 'brainbase-source-import': {
42
+ id: 'brainbase-source-import',
43
+ title: 'Brainbaseソース取り込み',
44
+ description: 'ローカルソースのメタデータを集め、Brainbase sourcesへ取り込み、レビュー候補を抽出する。',
45
+ body: [
46
+ '## 目的',
47
+ '',
48
+ 'ユーザーがBrainbaseオンボーディングでメール、カレンダー、ドライブ/ドキュメント、タスク、ローカルメモを対象にした時に使う。',
49
+ '',
50
+ '## 手順',
51
+ '',
52
+ '1. ドライブフォルダ、ローカルフォルダ、カレンダー、タスクプロジェクト、メールアカウントを読む前に、明示的な許可範囲を確認する。',
53
+ '2. まずメタデータ優先で集める。Google Workspaceでは、利用可能ならローカルのGoG系コレクタを使い、出力は `sources/` 配下に置く。',
54
+ '3. 集めたJSONを `brainbase onboard:import --source gmail|calendar|drive|local --from <file>` で取り込む。',
55
+ '4. `brainbase onboard:extract --self-email <email> --write` で決定的なレビュー候補を抽出する。',
56
+ '5. 昇格前に、候補id、provenance count、source areaをユーザーに見せる。',
57
+ '',
58
+ '## 安全ルール',
59
+ '',
60
+ `- ${SECRET_RULE}`,
61
+ '- ユーザーが抜粋の利用を明示的に承認しない限り、メール本文全体、予定説明全文、ファイル本文を集めない。',
62
+ '- ドライブ全体やホームディレクトリ全体をスキャンしない。明示的な許可リストを使う。',
63
+ '- import/extractはcanonical memoryではない。二次ソース記録とレビュー候補を作るだけにする。'
64
+ ].join('\n')
65
+ },
66
+ 'brainbase-candidate-review': {
67
+ id: 'brainbase-candidate-review',
68
+ title: 'Brainbase候補レビュー',
69
+ description: 'Brainbase候補をユーザーと確認し、承認されたものだけを個人SSOTへ昇格する。',
70
+ body: [
71
+ '## 目的',
72
+ '',
73
+ '面談やsource extractionからBrainbase候補が作られた時に使う。',
74
+ '',
75
+ '## 手順',
76
+ '',
77
+ '1. 候補をself、value、project、person、organization、relationship、decision、next actionに分類する。',
78
+ '2. private sourceの生データをそのまま出さず、候補id、平易な要約、provenanceを提示する。',
79
+ '3. どの候補idを承認、却下、修正するかをユーザーに確認する。',
80
+ '4. まず `brainbase onboard:apply --from <candidate-file> --select <id>` でdry-runする。手入力の事実には `brainbase onboard:seed` を使う。',
81
+ '5. `--write` は明示的な承認後にだけ使い、canonical filesへ書き込む。',
82
+ '6. `brainbase doctor` を実行し、必要に応じてBrainbase MCPの `get_context` または `search` で承認済み事実が見えることを確認する。',
83
+ '',
84
+ '## 安全ルール',
85
+ '',
86
+ `- ${SECRET_RULE}`,
87
+ '- 頻出しているだけの候補を自動昇格しない。',
88
+ '- 却下または不確実な候補はcanonical SSOTに入れない。',
89
+ '- source由来の推測と、ユーザー承認済みのdurable memoryを分ける。'
90
+ ].join('\n')
91
+ },
92
+ 'brainbase-daily-routines': {
93
+ id: 'brainbase-daily-routines',
94
+ title: 'Brainbase日次ルーティン',
95
+ description: 'ユーザーのエージェントスケジューラ向けに、個人用Brainbase ohayo、oyasumi、retroを生成する。',
96
+ body: [
97
+ '## 目的',
98
+ '',
99
+ 'ユーザーがBrainbaseを一度きりの設定ではなく、継続的な個人オペレーティングループにしたい時に使う。',
100
+ '',
101
+ '## 手順',
102
+ '',
103
+ '1. `brainbase onboard:routines --target codex|claude` でルーティン定義を生成する。',
104
+ '2. 必要に応じて `--routines ohayo,oyasumi,retro` またはsubsetを使う。',
105
+ '3. `--ohayo-hour`、`--oyasumi-hour`、`--retro-dow`、`--retro-hour` で実行時刻を調整する。',
106
+ '4. スケジューラに登録する前に、生成されたpromptとscheduleをユーザーと確認する。',
107
+ '5. 各ルーティンは、ユーザー自身のローカルBrainbase MCP contextと承認済みソースだけに限定する。',
108
+ '',
109
+ '## 安全ルール',
110
+ '',
111
+ `- ${SECRET_RULE}`,
112
+ '- 明示的な確認なしに、メッセージ送信、公開、カレンダー変更、レコード削除をしない。',
113
+ '- 認証やcollectorの失敗は「データ未取得」と扱い、「対象ゼロ」とは扱わない。',
114
+ '- ルーティン生成はlive scheduler登録ではない。登録はユーザーが別ステップとして行う。'
115
+ ].join('\n')
116
+ }
117
+ };
118
+ const BANNED_PUBLIC_TERMS = [
119
+ 'slack',
120
+ 'sns',
121
+ 'nocodb',
122
+ 'vibepro',
123
+ 'infisical',
124
+ 'lightsail',
125
+ 'hosted backend',
126
+ 'server operations',
127
+ 'server operation',
128
+ 'unson'
129
+ ];
130
+ export function parseSkillTarget(value) {
131
+ if (value === 'codex' || value === 'claude' || value === 'portable') {
132
+ return value;
133
+ }
134
+ throw new Error('onboard:skills requires --target codex|claude|portable');
135
+ }
136
+ export function parseSkillIds(value) {
137
+ if (!value) {
138
+ return [...ALL_BRAINBASE_SKILLS];
139
+ }
140
+ const requested = new Set(value.split(',').map((part) => part.trim()).filter(Boolean));
141
+ const unknown = [...requested].filter((id) => !isBrainbaseSkillId(id));
142
+ if (unknown.length > 0) {
143
+ throw new Error(`Unknown Brainbase skill id(s): ${unknown.join(', ')}. Expected one of: ${ALL_BRAINBASE_SKILLS.join(', ')}`);
144
+ }
145
+ const selected = ALL_BRAINBASE_SKILLS.filter((id) => requested.has(id));
146
+ if (selected.length === 0) {
147
+ throw new Error(`--skills must include at least one of: ${ALL_BRAINBASE_SKILLS.join(', ')}`);
148
+ }
149
+ return selected;
150
+ }
151
+ export function buildSkillBundle(target, ids = ALL_BRAINBASE_SKILLS) {
152
+ const skills = ids.map((id) => buildSkillFile(target, SKILL_DEFINITIONS[id]));
153
+ return {
154
+ target,
155
+ goal: '個人のコーディングエージェント運用向けに、公開safeなBrainbaseオンボーディングskillsを生成する。',
156
+ canonicalWrites: false,
157
+ liveConfigWrites: false,
158
+ skills,
159
+ safetyRules: [
160
+ SECRET_RULE,
161
+ '生成されるskillsは個人スコープかつローカルファーストに限定する。',
162
+ '生成されるskillsは内部Brainbase運用からコピーしない。',
163
+ 'このコマンドはportableなSKILL.mdを表示または書き出すだけで、live agent configurationは変更しない。'
164
+ ],
165
+ nextSteps: [
166
+ `生成された ${target} skill files を確認する。`,
167
+ '対象エージェントのskill directoryへ配置するか、生成ディレクトリをエージェントに参照させる。',
168
+ '`brainbase onboard:agent` を実行し、オンボーディング面談を続ける。'
169
+ ]
170
+ };
171
+ }
172
+ export function renderSkillsMarkdown(bundle, outDir) {
173
+ const lines = ['# Brainbase公開オンボーディングSkills', ''];
174
+ lines.push(`- 対象: ${bundle.target}`);
175
+ lines.push(`- Skills: ${bundle.skills.map((skill) => skill.id).join(', ')}`);
176
+ lines.push(`- canonical writes: ${bundle.canonicalWrites}`);
177
+ lines.push(`- live config writes: ${bundle.liveConfigWrites}`);
178
+ if (outDir) {
179
+ lines.push(`- 出力ディレクトリ: ${outDir}`);
180
+ }
181
+ lines.push('', '## ファイル');
182
+ for (const skill of bundle.skills) {
183
+ lines.push('', `### ${skill.id}`, '');
184
+ lines.push(`- 推奨パス: ${skill.recommendedPath}`);
185
+ lines.push(`- 相対パス: ${skill.relativePath}`);
186
+ lines.push('', '```markdown', skill.content.trimEnd(), '```');
187
+ }
188
+ lines.push('', '## 安全ルール');
189
+ for (const rule of bundle.safetyRules) {
190
+ lines.push(`- ${rule}`);
191
+ }
192
+ lines.push('', '## 次のステップ');
193
+ for (const step of bundle.nextSteps) {
194
+ lines.push(`- ${step}`);
195
+ }
196
+ return `${lines.join('\n')}\n`;
197
+ }
198
+ export function assertPublicSafeSkillBundle(bundle) {
199
+ const haystack = bundle.skills.map((skill) => skill.content).join('\n').toLowerCase();
200
+ for (const term of BANNED_PUBLIC_TERMS) {
201
+ if (haystack.includes(term)) {
202
+ throw new Error(`Generated public Brainbase skills contain banned internal term: ${term}`);
203
+ }
204
+ }
205
+ for (const skill of bundle.skills) {
206
+ if (!skill.content.includes(SECRET_RULE)) {
207
+ throw new Error(`Generated skill ${skill.id} is missing the no-secrets safety rule.`);
208
+ }
209
+ }
210
+ }
211
+ function buildSkillFile(target, definition) {
212
+ const relativePath = `${definition.id}/SKILL.md`;
213
+ return {
214
+ id: definition.id,
215
+ description: definition.description,
216
+ relativePath,
217
+ recommendedPath: `${TARGET_BASE_PATHS[target]}/${relativePath}`,
218
+ content: renderSkillContent(definition)
219
+ };
220
+ }
221
+ function renderSkillContent(definition) {
222
+ return [
223
+ '---',
224
+ `name: ${definition.id}`,
225
+ `description: ${definition.description}`,
226
+ '---',
227
+ '',
228
+ `# ${definition.title}`,
229
+ '',
230
+ definition.body,
231
+ ''
232
+ ].join('\n');
233
+ }
234
+ function isBrainbaseSkillId(value) {
235
+ return ALL_BRAINBASE_SKILLS.includes(value);
236
+ }
package/dist/ssot.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { PersonalOs } from './types.js';
2
+ export declare function initializePersonalOs(dataDir: string): Promise<void>;
3
+ export declare function loadPersonalOs(dataDir: string): Promise<PersonalOs>;
4
+ export declare function mutatePersonalOs(dataDir: string, mutator: (current: PersonalOs) => PersonalOs | Promise<PersonalOs>): Promise<PersonalOs>;