@thoughtfree/mcp-server 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * ThoughtFree REST API client.
3
+ * Lightweight HTTP wrapper — no SvelteKit dependencies.
4
+ */
5
+ import type { Thought, Category, Stats, ListResponse, SearchResponse } from './types.js';
6
+ export declare class ThoughtFreeClient {
7
+ private baseUrl;
8
+ private apiKey;
9
+ constructor(baseUrl: string, apiKey: string);
10
+ private request;
11
+ listThoughts(params?: {
12
+ cursor?: string;
13
+ limit?: number;
14
+ category?: string;
15
+ source?: string;
16
+ type?: string;
17
+ isStarred?: boolean;
18
+ isArchived?: boolean;
19
+ }): Promise<ListResponse<Thought>>;
20
+ getThought(id: string): Promise<Thought>;
21
+ createThought(data: {
22
+ content: string;
23
+ type?: string;
24
+ category?: string;
25
+ status?: string;
26
+ userTags?: string[];
27
+ dueDate?: string;
28
+ priority?: string;
29
+ source?: string;
30
+ autoClassify?: boolean;
31
+ }): Promise<Thought>;
32
+ updateThought(id: string, data: {
33
+ content?: string;
34
+ category?: string;
35
+ status?: string;
36
+ userTags?: string[];
37
+ isStarred?: boolean;
38
+ isArchived?: boolean;
39
+ dueDate?: string;
40
+ priority?: string;
41
+ }): Promise<Thought>;
42
+ searchThoughts(query: string, params?: {
43
+ mode?: 'semantic' | 'fulltext' | 'hybrid';
44
+ limit?: number;
45
+ threshold?: number;
46
+ category?: string;
47
+ }): Promise<SearchResponse>;
48
+ queryBrain(question: string): Promise<{
49
+ answer: string;
50
+ }>;
51
+ listCategories(): Promise<{
52
+ categories: Category[];
53
+ }>;
54
+ getStats(): Promise<Stats>;
55
+ }
package/dist/client.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * ThoughtFree REST API client.
3
+ * Lightweight HTTP wrapper — no SvelteKit dependencies.
4
+ */
5
+ export class ThoughtFreeClient {
6
+ baseUrl;
7
+ apiKey;
8
+ constructor(baseUrl, apiKey) {
9
+ this.baseUrl = baseUrl.replace(/\/$/, '');
10
+ this.apiKey = apiKey;
11
+ }
12
+ async request(path, options = {}) {
13
+ const url = `${this.baseUrl}${path}`;
14
+ const res = await fetch(url, {
15
+ ...options,
16
+ headers: {
17
+ 'Authorization': `Bearer ${this.apiKey}`,
18
+ 'Content-Type': 'application/json',
19
+ ...options.headers
20
+ }
21
+ });
22
+ if (!res.ok) {
23
+ const body = await res.text().catch(() => '');
24
+ throw new Error(`ThoughtFree API error ${res.status}: ${body || res.statusText}`);
25
+ }
26
+ return res.json();
27
+ }
28
+ // ─── Thoughts ────────────────────────────────────────────
29
+ async listThoughts(params) {
30
+ const searchParams = new URLSearchParams();
31
+ if (params?.cursor)
32
+ searchParams.set('cursor', params.cursor);
33
+ if (params?.limit)
34
+ searchParams.set('limit', String(params.limit));
35
+ if (params?.category)
36
+ searchParams.set('category', params.category);
37
+ if (params?.source)
38
+ searchParams.set('source', params.source);
39
+ if (params?.type)
40
+ searchParams.set('type', params.type);
41
+ if (params?.isStarred)
42
+ searchParams.set('isStarred', 'true');
43
+ if (params?.isArchived)
44
+ searchParams.set('isArchived', 'true');
45
+ const qs = searchParams.toString();
46
+ return this.request(`/api/v1/thoughts${qs ? `?${qs}` : ''}`);
47
+ }
48
+ async getThought(id) {
49
+ return this.request(`/api/v1/thoughts/${id}`);
50
+ }
51
+ async createThought(data) {
52
+ return this.request('/api/v1/thoughts', {
53
+ method: 'POST',
54
+ body: JSON.stringify(data)
55
+ });
56
+ }
57
+ async updateThought(id, data) {
58
+ return this.request(`/api/v1/thoughts/${id}`, {
59
+ method: 'PATCH',
60
+ body: JSON.stringify(data)
61
+ });
62
+ }
63
+ // ─── Search ──────────────────────────────────────────────
64
+ async searchThoughts(query, params) {
65
+ const searchParams = new URLSearchParams({ q: query });
66
+ if (params?.mode)
67
+ searchParams.set('mode', params.mode);
68
+ if (params?.limit)
69
+ searchParams.set('limit', String(params.limit));
70
+ if (params?.threshold)
71
+ searchParams.set('threshold', String(params.threshold));
72
+ if (params?.category)
73
+ searchParams.set('category', params.category);
74
+ return this.request(`/api/v1/thoughts/search?${searchParams}`);
75
+ }
76
+ // ─── Query Brain (RAG) ───────────────────────────────────
77
+ async queryBrain(question) {
78
+ return this.request('/api/ai/query-thoughts', {
79
+ method: 'POST',
80
+ body: JSON.stringify({ question })
81
+ });
82
+ }
83
+ // ─── Categories ──────────────────────────────────────────
84
+ async listCategories() {
85
+ return this.request('/api/v1/categories');
86
+ }
87
+ // ─── Stats ───────────────────────────────────────────────
88
+ async getStats() {
89
+ return this.request('/api/v1/stats');
90
+ }
91
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ThoughtFree MCP Server
3
+ *
4
+ * Exposes your thought database as tools and resources accessible
5
+ * from any MCP-compatible AI agent (Claude Desktop, Cursor, VS Code, etc.).
6
+ */
7
+ export declare function main(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ThoughtFree MCP Server
3
+ *
4
+ * Exposes your thought database as tools and resources accessible
5
+ * from any MCP-compatible AI agent (Claude Desktop, Cursor, VS Code, etc.).
6
+ */
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { ThoughtFreeClient } from './client.js';
10
+ // Tools
11
+ import { registerSearchTool } from './tools/search.js';
12
+ import { registerCreateTool } from './tools/create.js';
13
+ import { registerListTool } from './tools/list.js';
14
+ import { registerGetTool } from './tools/get.js';
15
+ import { registerUpdateTool } from './tools/update.js';
16
+ import { registerQueryBrainTool } from './tools/query-brain.js';
17
+ import { registerCategoriesTool } from './tools/categories.js';
18
+ import { registerStatsTool } from './tools/stats.js';
19
+ // Resources
20
+ import { registerRecentResource } from './resources/recent.js';
21
+ import { registerStarredResource } from './resources/starred.js';
22
+ import { registerByCategoryResource } from './resources/by-category.js';
23
+ export async function main() {
24
+ const apiKey = process.env.THOUGHTFREE_API_KEY;
25
+ const apiUrl = process.env.THOUGHTFREE_API_URL || 'https://thoughtfree.io';
26
+ if (!apiKey) {
27
+ console.error('Error: THOUGHTFREE_API_KEY environment variable is required.');
28
+ console.error('Create an API key at: ThoughtFree > Settings > API Keys');
29
+ process.exit(1);
30
+ }
31
+ const client = new ThoughtFreeClient(apiUrl, apiKey);
32
+ const server = new McpServer({
33
+ name: 'thoughtfree',
34
+ version: '0.1.0'
35
+ });
36
+ // Register all tools
37
+ registerSearchTool(server, client);
38
+ registerCreateTool(server, client);
39
+ registerListTool(server, client);
40
+ registerGetTool(server, client);
41
+ registerUpdateTool(server, client);
42
+ registerQueryBrainTool(server, client);
43
+ registerCategoriesTool(server, client);
44
+ registerStatsTool(server, client);
45
+ // Register all resources
46
+ registerRecentResource(server, client);
47
+ registerStarredResource(server, client);
48
+ registerByCategoryResource(server, client);
49
+ // Connect via stdio transport
50
+ const transport = new StdioServerTransport();
51
+ await server.connect(transport);
52
+ console.error('ThoughtFree MCP Server running on stdio');
53
+ console.error(` API URL: ${apiUrl}`);
54
+ console.error(` Tools: 8 | Resources: 3`);
55
+ }
56
+ main().catch((err) => {
57
+ console.error('Fatal error:', err);
58
+ process.exit(1);
59
+ });
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerByCategoryResource(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,36 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export function registerByCategoryResource(server, client) {
3
+ server.registerResource('thoughts-by-category', new ResourceTemplate('thoughtfree://category/{categoryKey}', {
4
+ list: async () => {
5
+ try {
6
+ const result = await client.listCategories();
7
+ return {
8
+ resources: result.categories
9
+ .filter((c) => c.thoughtCount > 0)
10
+ .map((c) => ({
11
+ uri: `thoughtfree://category/${c.key}`,
12
+ name: `${c.emoji} ${c.label} (${c.thoughtCount} thoughts)`
13
+ }))
14
+ };
15
+ }
16
+ catch {
17
+ return { resources: [] };
18
+ }
19
+ }
20
+ }), {
21
+ title: 'Thoughts by Category',
22
+ description: 'Thoughts filtered by a specific category',
23
+ mimeType: 'application/json'
24
+ }, async (uri, { categoryKey }) => {
25
+ const result = await client.listThoughts({
26
+ category: categoryKey,
27
+ limit: 50
28
+ });
29
+ return {
30
+ contents: [{
31
+ uri: uri.href,
32
+ text: JSON.stringify(result.data, null, 2)
33
+ }]
34
+ };
35
+ });
36
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerRecentResource(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,15 @@
1
+ export function registerRecentResource(server, client) {
2
+ server.registerResource('recent-thoughts', 'thoughtfree://recent', {
3
+ title: 'Recent Thoughts',
4
+ description: 'Your 25 most recently captured thoughts',
5
+ mimeType: 'application/json'
6
+ }, async (uri) => {
7
+ const result = await client.listThoughts({ limit: 25 });
8
+ return {
9
+ contents: [{
10
+ uri: uri.href,
11
+ text: JSON.stringify(result.data, null, 2)
12
+ }]
13
+ };
14
+ });
15
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerStarredResource(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,15 @@
1
+ export function registerStarredResource(server, client) {
2
+ server.registerResource('starred-thoughts', 'thoughtfree://starred', {
3
+ title: 'Starred Thoughts',
4
+ description: 'All thoughts you\'ve marked as important',
5
+ mimeType: 'application/json'
6
+ }, async (uri) => {
7
+ const result = await client.listThoughts({ isStarred: true, limit: 100 });
8
+ return {
9
+ contents: [{
10
+ uri: uri.href,
11
+ text: JSON.stringify(result.data, null, 2)
12
+ }]
13
+ };
14
+ });
15
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerCategoriesTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export function registerCategoriesTool(server, client) {
3
+ server.registerTool('list_categories', {
4
+ title: 'List Categories',
5
+ description: 'List all thought categories (both predefined and custom) with emoji, label, description, and thought count per category.',
6
+ inputSchema: z.object({})
7
+ }, async () => {
8
+ try {
9
+ const result = await client.listCategories();
10
+ if (result.categories.length === 0) {
11
+ return { content: [{ type: 'text', text: 'No categories found.' }] };
12
+ }
13
+ const formatted = result.categories.map((c) => `${c.emoji} ${c.label} (${c.key}) — ${c.thoughtCount} thoughts${c.isCustom ? ' [custom]' : ''}${c.description ? `\n ${c.description}` : ''}`).join('\n');
14
+ return { content: [{ type: 'text', text: `Categories:\n\n${formatted}` }] };
15
+ }
16
+ catch (err) {
17
+ return { content: [{ type: 'text', text: `Failed to list categories: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerCreateTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ export function registerCreateTool(server, client) {
3
+ server.registerTool('create_thought', {
4
+ title: 'Create Thought',
5
+ description: 'Capture a new thought. Thoughts can be text notes, ideas, tasks, links, etc. Auto-tagged with source "mcp" for provenance.',
6
+ inputSchema: z.object({
7
+ content: z.string().describe('The thought content'),
8
+ type: z.enum(['text', 'voice', 'image', 'file', 'link']).default('text').describe('Thought type'),
9
+ category: z.string().optional().describe('Category (e.g., thought, idea, insight, bug, feature)'),
10
+ userTags: z.array(z.string()).optional().describe('Tags to attach'),
11
+ dueDate: z.string().optional().describe('Due date in YYYY-MM-DD format (for tasks)'),
12
+ priority: z.enum(['high', 'medium', 'low']).optional().describe('Priority level'),
13
+ autoClassify: z.boolean().default(true).describe('Let AI classify the thought automatically')
14
+ })
15
+ }, async ({ content, type, category, userTags, dueDate, priority, autoClassify }) => {
16
+ try {
17
+ const result = await client.createThought({
18
+ content,
19
+ type,
20
+ category,
21
+ userTags,
22
+ dueDate,
23
+ priority,
24
+ source: 'mcp',
25
+ autoClassify
26
+ });
27
+ return {
28
+ content: [{
29
+ type: 'text',
30
+ text: `Thought created successfully.\nID: ${result.id}\nType: ${result.type}\nCategory: ${result.category || 'uncategorized'}${result.dueDate ? `\nDue: ${result.dueDate}` : ''}${result.priority ? `\nPriority: ${result.priority}` : ''}\nCreated: ${result.createdAt}`
31
+ }]
32
+ };
33
+ }
34
+ catch (err) {
35
+ return { content: [{ type: 'text', text: `Failed to create thought: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerGetTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ export function registerGetTool(server, client) {
3
+ server.registerTool('get_thought', {
4
+ title: 'Get Thought',
5
+ description: 'Fetch a specific thought by its server ID. Returns the full thought object with all fields.',
6
+ inputSchema: z.object({
7
+ id: z.string().describe('The thought\'s server UUID')
8
+ })
9
+ }, async ({ id }) => {
10
+ try {
11
+ const t = await client.getThought(id);
12
+ const lines = [
13
+ `Content: ${t.content}`,
14
+ `ID: ${t.id}`,
15
+ `Type: ${t.type}`,
16
+ `Category: ${t.category || 'uncategorized'}`,
17
+ `Status: ${t.status || 'none'}`,
18
+ `Starred: ${t.isStarred ? 'yes' : 'no'}`,
19
+ `Archived: ${t.isArchived ? 'yes' : 'no'}`,
20
+ t.userTags.length ? `Tags: ${t.userTags.join(', ')}` : null,
21
+ t.dueDate ? `Due: ${t.dueDate}` : null,
22
+ t.priority ? `Priority: ${t.priority}` : null,
23
+ t.completedAt ? `Completed: ${t.completedAt}` : null,
24
+ t.source ? `Source: ${t.source}` : null,
25
+ `Created: ${t.createdAt}`,
26
+ `Updated: ${t.updatedAt}`
27
+ ].filter(Boolean).join('\n');
28
+ return { content: [{ type: 'text', text: lines }] };
29
+ }
30
+ catch (err) {
31
+ return { content: [{ type: 'text', text: `Failed to get thought: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
32
+ }
33
+ });
34
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerListTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export function registerListTool(server, client) {
3
+ server.registerTool('list_thoughts', {
4
+ title: 'List Thoughts',
5
+ description: 'Browse thoughts with optional filters and pagination. Returns newest first.',
6
+ inputSchema: z.object({
7
+ category: z.string().optional().describe('Filter by category'),
8
+ type: z.string().optional().describe('Filter by type (text, voice, image, file, link)'),
9
+ isStarred: z.boolean().optional().describe('Only show starred thoughts'),
10
+ isArchived: z.boolean().optional().describe('Only show archived thoughts'),
11
+ limit: z.number().min(1).max(100).default(20).describe('Number of thoughts to return'),
12
+ cursor: z.string().optional().describe('Pagination cursor from previous response')
13
+ })
14
+ }, async ({ category, type, isStarred, isArchived, limit, cursor }) => {
15
+ try {
16
+ const result = await client.listThoughts({ category, type, isStarred, isArchived, limit, cursor });
17
+ if (result.data.length === 0) {
18
+ return { content: [{ type: 'text', text: 'No thoughts found matching the filters.' }] };
19
+ }
20
+ const formatted = result.data.map((t, i) => `${i + 1}. ${t.category ? `[${t.category}] ` : ''}${t.isStarred ? '★ ' : ''}${t.content.slice(0, 200)}${t.content.length > 200 ? '...' : ''}\n ID: ${t.id} | Type: ${t.type} | Created: ${t.createdAt}${t.userTags.length ? ` | Tags: ${t.userTags.join(', ')}` : ''}`).join('\n\n');
21
+ let text = `Showing ${result.data.length} thoughts:\n\n${formatted}`;
22
+ if (result.hasMore && result.cursor) {
23
+ text += `\n\n--- More results available. Use cursor: "${result.cursor}" ---`;
24
+ }
25
+ return { content: [{ type: 'text', text }] };
26
+ }
27
+ catch (err) {
28
+ return { content: [{ type: 'text', text: `Failed to list thoughts: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerQueryBrainTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ export function registerQueryBrainTool(server, client) {
3
+ server.registerTool('query_brain', {
4
+ title: 'Query Brain',
5
+ description: 'Ask a natural language question about your thoughts. Uses semantic search to find relevant context and an AI model to synthesize an answer based on your thought history.',
6
+ inputSchema: z.object({
7
+ question: z.string().describe('Your question about your thoughts')
8
+ })
9
+ }, async ({ question }) => {
10
+ try {
11
+ const result = await client.queryBrain(question);
12
+ return { content: [{ type: 'text', text: result.answer }] };
13
+ }
14
+ catch (err) {
15
+ return { content: [{ type: 'text', text: `Query failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerSearchTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ export function registerSearchTool(server, client) {
3
+ server.registerTool('search_thoughts', {
4
+ title: 'Search Thoughts',
5
+ description: 'Search your thought database using semantic (AI-powered meaning), full-text, or hybrid search. Returns the most relevant thoughts with similarity scores.',
6
+ inputSchema: z.object({
7
+ query: z.string().describe('The search query'),
8
+ mode: z.enum(['semantic', 'fulltext', 'hybrid']).default('hybrid').describe('Search mode: semantic (meaning), fulltext (exact terms), or hybrid (both)'),
9
+ limit: z.number().min(1).max(50).default(10).describe('Maximum number of results'),
10
+ category: z.string().optional().describe('Filter by category')
11
+ })
12
+ }, async ({ query, mode, limit, category }) => {
13
+ try {
14
+ const result = await client.searchThoughts(query, { mode, limit, category });
15
+ if (result.data.length === 0) {
16
+ return { content: [{ type: 'text', text: `No thoughts found matching "${query}".` }] };
17
+ }
18
+ const formatted = result.data.map((t, i) => `${i + 1}. [${t.matchType}] (score: ${t.score.toFixed(2)}) ${t.category ? `[${t.category}] ` : ''}${t.content.slice(0, 200)}${t.content.length > 200 ? '...' : ''}\n ID: ${t.id} | Created: ${t.createdAt}`).join('\n\n');
19
+ return {
20
+ content: [{
21
+ type: 'text',
22
+ text: `Found ${result.total} results for "${query}" (mode: ${result.mode}):\n\n${formatted}`
23
+ }]
24
+ };
25
+ }
26
+ catch (err) {
27
+ return { content: [{ type: 'text', text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerStatsTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ export function registerStatsTool(server, client) {
3
+ server.registerTool('get_stats', {
4
+ title: 'Get Stats',
5
+ description: 'Get summary statistics about your thought database: total count, category/type breakdown, starred/archived counts, date range, and embedding coverage.',
6
+ inputSchema: z.object({})
7
+ }, async () => {
8
+ try {
9
+ const s = await client.getStats();
10
+ const catBreakdown = Object.entries(s.byCategory)
11
+ .sort(([, a], [, b]) => b - a)
12
+ .map(([k, v]) => ` ${k}: ${v}`)
13
+ .join('\n');
14
+ const typeBreakdown = Object.entries(s.byType)
15
+ .sort(([, a], [, b]) => b - a)
16
+ .map(([k, v]) => ` ${k}: ${v}`)
17
+ .join('\n');
18
+ const lines = [
19
+ `Total thoughts: ${s.totalThoughts}`,
20
+ `Starred: ${s.starred}`,
21
+ `Archived: ${s.archived}`,
22
+ `With due date: ${s.withDueDate}`,
23
+ `Embedding coverage: ${s.embeddingCoverage}%`,
24
+ s.oldestThought ? `Oldest: ${s.oldestThought}` : null,
25
+ s.newestThought ? `Newest: ${s.newestThought}` : null,
26
+ '',
27
+ 'By category:',
28
+ catBreakdown || ' (none)',
29
+ '',
30
+ 'By type:',
31
+ typeBreakdown || ' (none)'
32
+ ].filter((l) => l !== null).join('\n');
33
+ return { content: [{ type: 'text', text: lines }] };
34
+ }
35
+ catch (err) {
36
+ return { content: [{ type: 'text', text: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
37
+ }
38
+ });
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThoughtFreeClient } from '../client.js';
3
+ export declare function registerUpdateTool(server: McpServer, client: ThoughtFreeClient): void;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateTool(server, client) {
3
+ server.registerTool('update_thought', {
4
+ title: 'Update Thought',
5
+ description: 'Update an existing thought\'s content, category, tags, priority, starred status, or due date. All fields are optional — only provided fields are changed.',
6
+ inputSchema: z.object({
7
+ id: z.string().describe('The thought\'s server UUID'),
8
+ content: z.string().optional().describe('New content'),
9
+ category: z.string().optional().describe('New category'),
10
+ status: z.string().optional().describe('New status'),
11
+ userTags: z.array(z.string()).optional().describe('New tags (replaces existing)'),
12
+ isStarred: z.boolean().optional().describe('Star or unstar'),
13
+ isArchived: z.boolean().optional().describe('Archive or unarchive'),
14
+ dueDate: z.string().optional().describe('New due date (YYYY-MM-DD) or empty to clear'),
15
+ priority: z.enum(['high', 'medium', 'low']).optional().describe('New priority')
16
+ })
17
+ }, async ({ id, ...updates }) => {
18
+ try {
19
+ const t = await client.updateThought(id, updates);
20
+ return {
21
+ content: [{
22
+ type: 'text',
23
+ text: `Thought updated successfully.\nID: ${t.id}\nContent: ${t.content.slice(0, 100)}${t.content.length > 100 ? '...' : ''}\nCategory: ${t.category || 'uncategorized'}\nUpdated: ${t.updatedAt}`
24
+ }]
25
+ };
26
+ }
27
+ catch (err) {
28
+ return { content: [{ type: 'text', text: `Failed to update thought: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,59 @@
1
+ export interface Thought {
2
+ id: string;
3
+ clientId: string;
4
+ content: string;
5
+ type: string;
6
+ category: string | null;
7
+ status: string | null;
8
+ source: string | null;
9
+ userTags: string[];
10
+ isStarred: boolean;
11
+ isArchived: boolean;
12
+ dueDate: string | null;
13
+ priority: string | null;
14
+ completedAt: string | null;
15
+ attachments: unknown[];
16
+ createdAt: string;
17
+ updatedAt: string;
18
+ }
19
+ export interface SearchResult {
20
+ id: string;
21
+ clientId: string;
22
+ content: string;
23
+ type: string;
24
+ category: string | null;
25
+ userTags: string[];
26
+ createdAt: string;
27
+ score: number;
28
+ matchType: 'semantic' | 'fulltext';
29
+ }
30
+ export interface Category {
31
+ key: string;
32
+ emoji: string;
33
+ label: string;
34
+ description: string | null;
35
+ isCustom: boolean;
36
+ thoughtCount: number;
37
+ }
38
+ export interface Stats {
39
+ totalThoughts: number;
40
+ byCategory: Record<string, number>;
41
+ byType: Record<string, number>;
42
+ starred: number;
43
+ archived: number;
44
+ withDueDate: number;
45
+ embeddingCoverage: number;
46
+ oldestThought: string | null;
47
+ newestThought: string | null;
48
+ }
49
+ export interface ListResponse<T> {
50
+ data: T[];
51
+ cursor: string | null;
52
+ hasMore: boolean;
53
+ }
54
+ export interface SearchResponse {
55
+ data: SearchResult[];
56
+ query: string;
57
+ mode: string;
58
+ total: number;
59
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@thoughtfree/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for ThoughtFree — access your thought database from any AI agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "thoughtfree-mcp": "./bin/thoughtfree-mcp.mjs"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "bin"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsx src/index.ts",
17
+ "prepublishOnly": "tsc"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "thoughtfree",
23
+ "ai",
24
+ "thoughts",
25
+ "claude",
26
+ "cursor"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/WholenessMedia/thoughtfree.git",
31
+ "directory": "packages/mcp-server"
32
+ },
33
+ "homepage": "https://thoughtfree.io",
34
+ "bugs": {
35
+ "url": "https://github.com/WholenessMedia/thoughtfree/issues"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.11.0",
42
+ "zod": "^3.25.0"
43
+ },
44
+ "devDependencies": {
45
+ "tsx": "^4.0.0",
46
+ "typescript": "^5.0.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "license": "MIT"
52
+ }