├── .env.example ├── .npmignore ├── tsconfig.json ├── .gitignore ├── src ├── index.ts ├── types │ └── tana-api.ts └── server │ ├── tana-client.ts │ └── tana-mcp-server.ts ├── LICENSE ├── package.json ├── examples └── example-usage.js └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | # Tana API Token (required) 2 | # Generate this in Tana: Settings > API Tokens 3 | TANA_API_TOKEN=your-tana-api-token-here 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source files 2 | src/ 3 | 4 | # Development files 5 | node_modules/ 6 | .git/ 7 | .github/ 8 | .gitignore 9 | 10 | # Environment files 11 | .env 12 | .env.example 13 | 14 | # Config files 15 | tsconfig.json 16 | 17 | # Examples (since they're included in the README) 18 | examples/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "outDir": "./dist", 9 | "sourceMap": true, 10 | "declaration": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules", "dist"] 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # Build outputs 5 | dist/ 6 | build/ 7 | 8 | # Environment variables 9 | .env 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea/ 20 | .vscode/ 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # OS specific 28 | .DS_Store 29 | Thumbs.db 30 | 31 | # Claude Code configuration 32 | CLAUDE.md -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Tana MCP Server 5 | * Main entry point for the MCP server that connects to Tana's Input API 6 | */ 7 | 8 | import { TanaMcpServer } from './server/tana-mcp-server'; 9 | 10 | // Get the API token from environment variables 11 | const apiToken = process.env.TANA_API_TOKEN; 12 | 13 | // Check if the API token is provided 14 | if (!apiToken) { 15 | console.error('Error: TANA_API_TOKEN environment variable is required'); 16 | process.exit(1); 17 | } 18 | 19 | // Optional custom endpoint from environment variables 20 | const endpoint = process.env.TANA_API_ENDPOINT; 21 | 22 | // Create and start the MCP server 23 | const server = new TanaMcpServer(apiToken, endpoint); 24 | 25 | // Start the server 26 | server.start().catch((error) => { 27 | console.error('Error starting Tana MCP server:', error); 28 | process.exit(1); 29 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tim McDonnell 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. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tana-mcp", 3 | "version": "1.2.0", 4 | "description": "A Model Context Protocol (MCP) server for Tana", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "tana-mcp": "dist/index.js" 8 | }, 9 | "files": [ 10 | "dist", 11 | "README.md", 12 | "LICENSE" 13 | ], 14 | "scripts": { 15 | "build": "tsc", 16 | "start": "node dist/index.js", 17 | "dev": "ts-node src/index.ts", 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "prepublishOnly": "npm run build" 20 | }, 21 | "keywords": [ 22 | "mcp", 23 | "tana", 24 | "api", 25 | "llm", 26 | "model-context-protocol", 27 | "tana-api" 28 | ], 29 | "author": "Tim McDonnell", 30 | "license": "MIT", 31 | "type": "commonjs", 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/tim-mcdonnell/tana-mcp.git" 35 | }, 36 | "dependencies": { 37 | "@modelcontextprotocol/sdk": "^1.12.1", 38 | "axios": "^1.7.7", 39 | "typescript": "^5.6.3", 40 | "zod": "^3.23.8" 41 | }, 42 | "devDependencies": { 43 | "@types/node": "^20.0.0", 44 | "ts-node": "^10.9.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/tana-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tana API Types 3 | * Based on documentation from https://tana.inc/docs/input-api 4 | */ 5 | 6 | // Basic node types 7 | export type DataType = 'plain' | 'reference' | 'date' | 'url' | 'boolean' | 'file'; 8 | 9 | export interface TanaSupertag { 10 | id: string; 11 | fields?: Record; 12 | } 13 | 14 | export interface TanaBaseNode { 15 | name?: string; 16 | description?: string; 17 | supertags?: TanaSupertag[]; 18 | children?: TanaNode[]; 19 | } 20 | 21 | export interface TanaPlainNode extends TanaBaseNode { 22 | dataType?: 'plain'; 23 | } 24 | 25 | export interface TanaReferenceNode { 26 | dataType: 'reference'; 27 | id: string; 28 | } 29 | 30 | export interface TanaDateNode extends TanaBaseNode { 31 | dataType: 'date'; 32 | name: string; // ISO 8601 format 33 | } 34 | 35 | export interface TanaUrlNode extends TanaBaseNode { 36 | dataType: 'url'; 37 | name: string; // URL string 38 | } 39 | 40 | export interface TanaBooleanNode extends TanaBaseNode { 41 | dataType: 'boolean'; 42 | name: string; 43 | value: boolean; 44 | } 45 | 46 | export interface TanaFileNode extends TanaBaseNode { 47 | dataType: 'file'; 48 | file: string; // base64 encoded file data 49 | filename: string; 50 | contentType: string; // MIME type 51 | } 52 | 53 | export interface TanaFieldNode { 54 | type: 'field'; 55 | attributeId: string; 56 | children?: TanaNode[]; 57 | } 58 | 59 | export type TanaNode = 60 | | TanaPlainNode 61 | | TanaReferenceNode 62 | | TanaDateNode 63 | | TanaUrlNode 64 | | TanaBooleanNode 65 | | TanaFileNode 66 | | TanaFieldNode; 67 | 68 | // API Request Types 69 | export interface TanaCreateNodesRequest { 70 | targetNodeId?: string; 71 | nodes: TanaNode[]; 72 | } 73 | 74 | export interface TanaSetNameRequest { 75 | targetNodeId: string; 76 | setName: string; 77 | } 78 | 79 | export type TanaAPIRequest = TanaCreateNodesRequest | TanaSetNameRequest; 80 | 81 | // API Response Types 82 | export interface TanaNodeResponse { 83 | nodeId: string; 84 | name?: string; 85 | description?: string; 86 | children?: TanaNodeResponse[]; 87 | } 88 | 89 | export interface TanaAPIResponse { 90 | children?: TanaNodeResponse[]; 91 | } 92 | 93 | // Configuration 94 | export interface TanaConfig { 95 | apiToken: string; 96 | endpoint?: string; 97 | } -------------------------------------------------------------------------------- /src/server/tana-client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tana API Client 3 | * A client for interacting with the Tana Input API 4 | */ 5 | 6 | import axios from 'axios'; 7 | import { 8 | TanaAPIRequest, 9 | TanaAPIResponse, 10 | TanaConfig, 11 | TanaCreateNodesRequest, 12 | TanaNode, 13 | TanaNodeResponse, 14 | TanaSetNameRequest 15 | } from '../types/tana-api'; 16 | 17 | export class TanaClient { 18 | private readonly apiToken: string; 19 | private readonly endpoint: string; 20 | 21 | constructor(config: TanaConfig) { 22 | this.apiToken = config.apiToken; 23 | this.endpoint = config.endpoint || 'https://europe-west1-tagr-prod.cloudfunctions.net/addToNodeV2'; 24 | } 25 | 26 | /** 27 | * Create nodes in Tana 28 | * @param targetNodeId Optional target node ID to add nodes under 29 | * @param nodes Array of nodes to create 30 | * @returns The created nodes 31 | */ 32 | async createNodes(targetNodeId: string | undefined, nodes: TanaNode[]): Promise { 33 | if (nodes.length > 100) { 34 | throw new Error('Maximum of 100 nodes can be created in a single request'); 35 | } 36 | 37 | const request: TanaCreateNodesRequest = { 38 | targetNodeId, 39 | nodes 40 | }; 41 | 42 | const response = await this.makeRequest(request); 43 | return response.children || []; 44 | } 45 | 46 | /** 47 | * Create a single node in Tana 48 | * @param targetNodeId Optional target node ID to add the node under 49 | * @param node Node to create 50 | * @returns The created node 51 | */ 52 | async createNode(targetNodeId: string | undefined, node: TanaNode): Promise { 53 | const nodes = await this.createNodes(targetNodeId, [node]); 54 | if (nodes.length === 0) { 55 | throw new Error('Failed to create node'); 56 | } 57 | return nodes[0]; 58 | } 59 | 60 | /** 61 | * Set the name of a node in Tana 62 | * @param targetNodeId ID of the node to rename 63 | * @param newName New name for the node 64 | * @returns The updated node 65 | */ 66 | async setNodeName(targetNodeId: string, newName: string): Promise { 67 | const request: TanaSetNameRequest = { 68 | targetNodeId, 69 | setName: newName 70 | }; 71 | 72 | const response = await this.makeRequest(request); 73 | 74 | // Handle the different response format for setName 75 | if (response.children && response.children.length > 0) { 76 | return response.children[0]; 77 | } 78 | 79 | // If the response doesn't match expected format, make a best guess 80 | return { nodeId: targetNodeId }; 81 | } 82 | 83 | /** 84 | * Make a request to the Tana API 85 | * @param request The request to send 86 | * @returns The API response 87 | */ 88 | private async makeRequest(request: TanaAPIRequest): Promise { 89 | try { 90 | const response = await axios.post(this.endpoint, request, { 91 | headers: { 92 | 'Authorization': `Bearer ${this.apiToken}`, 93 | 'Content-Type': 'application/json' 94 | } 95 | }); 96 | 97 | return response.data; 98 | } catch (error) { 99 | if (axios.isAxiosError(error) && error.response) { 100 | throw new Error(`Tana API error: ${error.response.status} ${error.response.statusText}`); 101 | } 102 | throw error; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /examples/example-usage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of how to use the Tana MCP server with an LLM client 3 | * 4 | * This demonstrates how an LLM like Claude might interact with the Tana API via MCP 5 | */ 6 | 7 | // This is pseudocode - actual implementation will depend on your MCP client library 8 | 9 | // 1. Connect to the Tana MCP server 10 | const tanaServer = connectToMcpServer({ 11 | name: 'Tana MCP Server', 12 | transport: 'stdio', // or other transport options 13 | }); 14 | 15 | // 2. Get available tools from the server 16 | const tools = await tanaServer.getTools(); 17 | console.log('Available tools:', tools.map(tool => tool.name)); 18 | 19 | // 3. Create a simple plain node 20 | const createResult = await tanaServer.callTool('create_plain_node', { 21 | name: 'My first node from MCP', 22 | description: 'Created via Model Context Protocol', 23 | }); 24 | console.log('Created node:', createResult); 25 | 26 | // 4. Get the node ID from the response 27 | const nodeId = JSON.parse(createResult.content[0].text).nodeId; 28 | 29 | // 5. Update the node name 30 | const updateResult = await tanaServer.callTool('set_node_name', { 31 | nodeId, 32 | newName: 'Updated node name', 33 | }); 34 | console.log('Updated node:', updateResult); 35 | 36 | // 6. Create a more complex structure with a date node as a child 37 | const complexResult = await tanaServer.callTool('create_node_structure', { 38 | node: { 39 | name: 'Parent node', 40 | description: 'This is a parent node with children', 41 | children: [ 42 | { 43 | dataType: 'date', 44 | name: '2023-12-25', 45 | description: 'Christmas Day', 46 | }, 47 | { 48 | name: 'Regular child node', 49 | }, 50 | { 51 | dataType: 'boolean', 52 | name: 'Task to complete', 53 | value: false, 54 | } 55 | ] 56 | } 57 | }); 58 | console.log('Created complex structure:', complexResult); 59 | 60 | // 7. Create a node with supertags 61 | const supertagResult = await tanaServer.callTool('create_plain_node', { 62 | name: 'Node with supertags', 63 | supertags: [ 64 | { 65 | id: 'your-supertag-id-here', 66 | fields: { 67 | 'field-id-1': 'field value 1', 68 | 'field-id-2': 'field value 2', 69 | } 70 | } 71 | ] 72 | }); 73 | console.log('Created node with supertags:', supertagResult); 74 | 75 | // 8. Create a field node 76 | const fieldNodeResult = await tanaServer.callTool('create_field_node', { 77 | targetNodeId: nodeId, // Using the ID from a previously created node 78 | attributeId: 'field-attribute-id', // ID of the field definition in Tana 79 | children: [ 80 | { 81 | name: 'Field value as a child node', 82 | } 83 | ] 84 | }); 85 | console.log('Created field node:', fieldNodeResult); 86 | 87 | // 9. Create a node with multiple supertags 88 | const multiSupertagResult = await tanaServer.callTool('create_plain_node', { 89 | name: 'Node with multiple supertags', 90 | supertags: [ 91 | { 92 | id: 'project-supertag-id', 93 | fields: { 94 | 'priority': 'high', 95 | 'status': 'in-progress' 96 | } 97 | }, 98 | { 99 | id: 'task-supertag-id', 100 | fields: { 101 | 'assigned-to': 'user-id-123' 102 | } 103 | } 104 | ] 105 | }); 106 | console.log('Created node with multiple supertags:', multiSupertagResult); 107 | 108 | // 10. Create a plain node with a field as a child using create_node_structure 109 | const plainWithFieldResult = await tanaServer.callTool('create_node_structure', { 110 | node: { 111 | name: 'Plain node with field child', 112 | children: [ 113 | { 114 | type: 'field', 115 | attributeId: 'due-date-field-id', 116 | children: [ 117 | { 118 | dataType: 'date', 119 | name: '2024-01-15' 120 | } 121 | ] 122 | }, 123 | { 124 | type: 'field', 125 | attributeId: 'assignee-field-id', 126 | children: [ 127 | { 128 | name: 'John Doe' 129 | } 130 | ] 131 | } 132 | ] 133 | } 134 | }); 135 | console.log('Created plain node with fields:', plainWithFieldResult); 136 | 137 | /** 138 | * In a real LLM conversation, this would look something like: 139 | * 140 | * User: "Create a new project in Tana called 'Website Redesign' with a deadline of December 15, 2023" 141 | * 142 | * LLM: [internally calls the MCP tools] 143 | * 144 | * const result = await tanaServer.callTool('create_node_structure', { 145 | * node: { 146 | * name: 'Website Redesign', 147 | * supertags: [{ id: 'project-tag-id' }], 148 | * children: [ 149 | * { 150 | * type: 'field', 151 | * attributeId: 'deadline-field-id', 152 | * children: [ 153 | * { 154 | * dataType: 'date', 155 | * name: '2023-12-15' 156 | * } 157 | * ] 158 | * } 159 | * ] 160 | * } 161 | * }); 162 | * 163 | * LLM: "I've created a new project in Tana called 'Website Redesign' with a 164 | * deadline set to December 15, 2023." 165 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tana MCP Server 2 | 3 | A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that connects to [Tana's Input API](https://tana.inc/docs/input-api), allowing Large Language Models (LLMs) and other MCP clients to create and manipulate data in Tana workspaces. 4 | 5 | 6 | Tana Server MCP server 7 | 8 | 9 | ## Features 10 | 11 | This MCP server provides comprehensive access to Tana's Input API with: 12 | 13 | ### 🛠️ Tools (11 available) 14 | - **Node Creation**: Create plain, reference, date, URL, checkbox, and file nodes 15 | - **Field Management**: Create and manage field nodes with structured data 16 | - **Complex Structures**: Build nested node hierarchies 17 | - **Schema Operations**: Create supertags and field definitions 18 | - **Node Updates**: Modify existing node names 19 | 20 | ### 💬 Prompts (4 templates) 21 | - **Task Creation**: Structured task creation with due dates and priorities 22 | - **Project Setup**: Complete project structures with goals and milestones 23 | - **Meeting Notes**: Formatted meeting notes with attendees and action items 24 | - **Knowledge Base**: Organized knowledge entries with categories and sources 25 | 26 | ### 📚 Resources (4 available) 27 | - **API Documentation**: Complete reference for Tana Input API 28 | - **Node Types Guide**: Detailed examples of all supported node types 29 | - **Usage Examples**: Common patterns and best practices 30 | - **Server Info**: Current status and configuration details 31 | 32 | ## Requirements 33 | 34 | - Node.js 18 or higher 35 | - A Tana workspace with API access enabled 36 | - Tana API token (generated from Tana settings) 37 | 38 | ## Installation 39 | 40 | ### Global Installation (Recommended) 41 | 42 | ```bash 43 | npm install -g tana-mcp 44 | ``` 45 | 46 | ### Local Installation 47 | 48 | ```bash 49 | npm install tana-mcp 50 | ``` 51 | 52 | ## Configuration 53 | 54 | ### Claude Desktop 55 | 56 | Add this to your Claude Desktop configuration file: 57 | 58 | **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` 59 | **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 60 | 61 | ```json 62 | { 63 | "mcpServers": { 64 | "tana-mcp": { 65 | "command": "npx", 66 | "args": ["-y", "tana-mcp"], 67 | "env": { 68 | "TANA_API_TOKEN": "your-api-token-here" 69 | } 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | ### Raycast 76 | 77 | 1. Install the MCP extension in Raycast 78 | 2. Open "Manage MCP Servers" command 79 | 3. Add a new server with this configuration: 80 | 81 | ```json 82 | { 83 | "tana-mcp": { 84 | "command": "npx", 85 | "args": ["-y", "tana-mcp"], 86 | "env": { 87 | "TANA_API_TOKEN": "your-api-token-here" 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | ### Other MCP Clients 94 | 95 | For other MCP-compatible clients, use: 96 | - **Command**: `npx -y tana-mcp` (or `tana-mcp` if installed globally) 97 | - **Environment**: `TANA_API_TOKEN=your-api-token-here` 98 | 99 | ## Getting Your Tana API Token 100 | 101 | 1. Open Tana in your browser 102 | 2. Go to Settings → API tokens 103 | 3. Create a new token with appropriate permissions 104 | 4. Copy the token and add it to your MCP client configuration 105 | 106 | ## Usage Examples 107 | 108 | Once configured, you can interact with Tana through your MCP client: 109 | 110 | ### Creating a Simple Node 111 | ``` 112 | Create a new node titled "Project Ideas" in my Tana workspace 113 | ``` 114 | 115 | ### Creating a Task 116 | ``` 117 | Create a task "Review Q4 budget" with high priority due next Friday 118 | ``` 119 | 120 | ### Creating a Project Structure 121 | ``` 122 | Create a project called "Website Redesign" with milestones for design, development, and launch 123 | ``` 124 | 125 | ### Using Prompts 126 | MCP clients that support prompts can use templates like: 127 | - `create-task` - Interactive task creation 128 | - `create-project` - Structured project setup 129 | - `create-meeting-notes` - Meeting documentation 130 | - `create-knowledge-entry` - Knowledge base entries 131 | 132 | ## API Limitations 133 | 134 | - Maximum 100 nodes per request 135 | - Rate limit: 1 request per second per token 136 | - Payload size: 5000 characters maximum 137 | - Workspace limit: 750,000 nodes 138 | 139 | ## Development 140 | 141 | ### Building from Source 142 | 143 | ```bash 144 | git clone https://github.com/tim-mcdonnell/tana-mcp.git 145 | cd tana-mcp 146 | npm install 147 | npm run build 148 | ``` 149 | 150 | ### Running in Development 151 | 152 | ```bash 153 | TANA_API_TOKEN=your-token npm run dev 154 | ``` 155 | 156 | ## Troubleshooting 157 | 158 | ### "Missing expected parameter key: items" (Raycast) 159 | This error was fixed in v1.2.0. Please update to the latest version. 160 | 161 | ### Connection Issues 162 | - Verify your API token is correct 163 | - Check that your workspace hasn't exceeded the 750k node limit 164 | - Ensure you're not exceeding the rate limit (1 request/second) 165 | 166 | ### Node Creation Failures 167 | - Verify the target node ID exists (if specified) 168 | - Check that supertag/field IDs are valid for your workspace 169 | - Ensure payload is under 5000 characters 170 | 171 | ## Contributing 172 | 173 | Contributions are welcome! Please feel free to submit a Pull Request. 174 | 175 | ## License 176 | 177 | MIT License - see LICENSE file for details 178 | 179 | ## Support 180 | 181 | For issues and feature requests, please use the [GitHub Issues](https://github.com/tim-mcdonnell/tana-mcp/issues) page. -------------------------------------------------------------------------------- /src/server/tana-mcp-server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tana MCP Server 3 | * An MCP server that connects to the Tana Input API 4 | */ 5 | 6 | import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; 7 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 8 | import { z } from 'zod'; 9 | import { TanaClient } from './tana-client'; 10 | import { 11 | TanaBooleanNode, 12 | TanaDateNode, 13 | TanaFileNode, 14 | TanaPlainNode, 15 | TanaReferenceNode, 16 | TanaSupertag, 17 | TanaUrlNode 18 | } from '../types/tana-api'; 19 | 20 | // Define Zod schemas for validating inputs 21 | const SupertagSchema = z.object({ 22 | id: z.string(), 23 | fields: z.record(z.string()).optional() 24 | }); 25 | 26 | // We need a recursive type for NodeSchema to handle field nodes and other nested structures 27 | const NodeSchema = z.lazy(() => 28 | z.object({ 29 | name: z.string().optional(), 30 | description: z.string().optional(), 31 | supertags: z.array(SupertagSchema).optional(), 32 | children: z.array(z.any()).optional(), // Will be validated by implementation 33 | // Fields for specific node types 34 | dataType: z.enum(['plain', 'reference', 'date', 'url', 'boolean', 'file']).optional(), 35 | id: z.string().optional(), // For reference nodes 36 | value: z.boolean().optional(), // For boolean nodes 37 | file: z.string().optional(), // For file nodes (base64) 38 | filename: z.string().optional(), // For file nodes 39 | contentType: z.string().optional(), // For file nodes 40 | // Field node properties 41 | type: z.literal('field').optional(), 42 | attributeId: z.string().optional() 43 | }) 44 | ); 45 | 46 | export class TanaMcpServer { 47 | private readonly server: McpServer; 48 | private readonly tanaClient: TanaClient; 49 | 50 | constructor(apiToken: string, endpoint?: string) { 51 | // Create the Tana client 52 | this.tanaClient = new TanaClient({ 53 | apiToken, 54 | endpoint 55 | }); 56 | 57 | // Create the MCP server with proper capabilities 58 | this.server = new McpServer({ 59 | name: 'Tana MCP Server', 60 | version: '1.2.0' 61 | }); 62 | 63 | // Register tools 64 | this.registerTools(); 65 | 66 | // Register prompts 67 | this.registerPrompts(); 68 | 69 | // Register resources 70 | this.registerResources(); 71 | } 72 | 73 | /** 74 | * Start the MCP server 75 | */ 76 | async start(): Promise { 77 | // Create the transport 78 | const transport = new StdioServerTransport(); 79 | 80 | // Connect the server to the transport 81 | await this.server.connect(transport); 82 | 83 | // Server is ready - no logging to stderr to avoid protocol interference 84 | } 85 | 86 | /** 87 | * Register tools for interacting with Tana 88 | */ 89 | private registerTools(): void { 90 | // Create a plain node tool 91 | this.server.tool( 92 | 'create_plain_node', 93 | { 94 | targetNodeId: z.string().optional(), 95 | name: z.string(), 96 | description: z.string().optional(), 97 | supertags: z.array(SupertagSchema).optional() 98 | }, 99 | async ({ targetNodeId, name, description, supertags }) => { 100 | try { 101 | const node: TanaPlainNode = { 102 | name, 103 | description, 104 | supertags 105 | }; 106 | 107 | const result = await this.tanaClient.createNode(targetNodeId, node); 108 | 109 | return { 110 | content: [ 111 | { 112 | type: 'text', 113 | text: JSON.stringify(result, null, 2) 114 | } 115 | ], 116 | isError: false 117 | }; 118 | } catch (error) { 119 | return { 120 | content: [ 121 | { 122 | type: 'text', 123 | text: `Error creating plain node: ${error instanceof Error ? error.message : String(error)}` 124 | } 125 | ], 126 | isError: true 127 | }; 128 | } 129 | } 130 | ); 131 | 132 | // Create a reference node tool 133 | this.server.tool( 134 | 'create_reference_node', 135 | { 136 | targetNodeId: z.string().optional(), 137 | referenceId: z.string() 138 | }, 139 | async ({ targetNodeId, referenceId }) => { 140 | try { 141 | const node: TanaReferenceNode = { 142 | dataType: 'reference', 143 | id: referenceId 144 | }; 145 | 146 | const result = await this.tanaClient.createNode(targetNodeId, node); 147 | 148 | return { 149 | content: [ 150 | { 151 | type: 'text', 152 | text: JSON.stringify(result, null, 2) 153 | } 154 | ], 155 | isError: false 156 | }; 157 | } catch (error) { 158 | return { 159 | content: [ 160 | { 161 | type: 'text', 162 | text: `Error creating reference node: ${error instanceof Error ? error.message : String(error)}` 163 | } 164 | ], 165 | isError: true 166 | }; 167 | } 168 | } 169 | ); 170 | 171 | // Create a date node tool 172 | this.server.tool( 173 | 'create_date_node', 174 | { 175 | targetNodeId: z.string().optional(), 176 | date: z.string(), 177 | description: z.string().optional(), 178 | supertags: z.array(SupertagSchema).optional() 179 | }, 180 | async ({ targetNodeId, date, description, supertags }) => { 181 | try { 182 | const node: TanaDateNode = { 183 | dataType: 'date', 184 | name: date, 185 | description, 186 | supertags 187 | }; 188 | 189 | const result = await this.tanaClient.createNode(targetNodeId, node); 190 | 191 | return { 192 | content: [ 193 | { 194 | type: 'text', 195 | text: JSON.stringify(result, null, 2) 196 | } 197 | ], 198 | isError: false 199 | }; 200 | } catch (error) { 201 | return { 202 | content: [ 203 | { 204 | type: 'text', 205 | text: `Error creating date node: ${error instanceof Error ? error.message : String(error)}` 206 | } 207 | ], 208 | isError: true 209 | }; 210 | } 211 | } 212 | ); 213 | 214 | // Create a URL node tool 215 | this.server.tool( 216 | 'create_url_node', 217 | { 218 | targetNodeId: z.string().optional(), 219 | url: z.string().url(), 220 | description: z.string().optional(), 221 | supertags: z.array(SupertagSchema).optional() 222 | }, 223 | async ({ targetNodeId, url, description, supertags }) => { 224 | try { 225 | const node: TanaUrlNode = { 226 | dataType: 'url', 227 | name: url, 228 | description, 229 | supertags 230 | }; 231 | 232 | const result = await this.tanaClient.createNode(targetNodeId, node); 233 | 234 | return { 235 | content: [ 236 | { 237 | type: 'text', 238 | text: JSON.stringify(result, null, 2) 239 | } 240 | ], 241 | isError: false 242 | }; 243 | } catch (error) { 244 | return { 245 | content: [ 246 | { 247 | type: 'text', 248 | text: `Error creating URL node: ${error instanceof Error ? error.message : String(error)}` 249 | } 250 | ], 251 | isError: true 252 | }; 253 | } 254 | } 255 | ); 256 | 257 | // Create a checkbox node tool 258 | this.server.tool( 259 | 'create_checkbox_node', 260 | { 261 | targetNodeId: z.string().optional(), 262 | name: z.string(), 263 | checked: z.boolean(), 264 | description: z.string().optional(), 265 | supertags: z.array(SupertagSchema).optional() 266 | }, 267 | async ({ targetNodeId, name, checked, description, supertags }) => { 268 | try { 269 | const node: TanaBooleanNode = { 270 | dataType: 'boolean', 271 | name, 272 | value: checked, 273 | description, 274 | supertags 275 | }; 276 | 277 | const result = await this.tanaClient.createNode(targetNodeId, node); 278 | 279 | return { 280 | content: [ 281 | { 282 | type: 'text', 283 | text: JSON.stringify(result, null, 2) 284 | } 285 | ], 286 | isError: false 287 | }; 288 | } catch (error) { 289 | return { 290 | content: [ 291 | { 292 | type: 'text', 293 | text: `Error creating checkbox node: ${error instanceof Error ? error.message : String(error)}` 294 | } 295 | ], 296 | isError: true 297 | }; 298 | } 299 | } 300 | ); 301 | 302 | // Create a file node tool 303 | this.server.tool( 304 | 'create_file_node', 305 | { 306 | targetNodeId: z.string().optional(), 307 | fileData: z.string(), // base64 encoded file data 308 | filename: z.string(), 309 | contentType: z.string(), 310 | description: z.string().optional(), 311 | supertags: z.array(SupertagSchema).optional() 312 | }, 313 | async ({ targetNodeId, fileData, filename, contentType, description, supertags }) => { 314 | try { 315 | const node: TanaFileNode = { 316 | dataType: 'file', 317 | file: fileData, 318 | filename, 319 | contentType, 320 | description, 321 | supertags 322 | }; 323 | 324 | const result = await this.tanaClient.createNode(targetNodeId, node); 325 | 326 | return { 327 | content: [ 328 | { 329 | type: 'text', 330 | text: JSON.stringify(result, null, 2) 331 | } 332 | ], 333 | isError: false 334 | }; 335 | } catch (error) { 336 | return { 337 | content: [ 338 | { 339 | type: 'text', 340 | text: `Error creating file node: ${error instanceof Error ? error.message : String(error)}` 341 | } 342 | ], 343 | isError: true 344 | }; 345 | } 346 | } 347 | ); 348 | 349 | // Create a field node tool 350 | this.server.tool( 351 | 'create_field_node', 352 | { 353 | targetNodeId: z.string().optional(), 354 | attributeId: z.string(), 355 | children: z.array(NodeSchema).optional() 356 | }, 357 | async ({ targetNodeId, attributeId, children }) => { 358 | try { 359 | // Properly type the field node according to TanaFieldNode interface 360 | const fieldNode = { 361 | type: 'field' as const, // Use 'as const' to ensure type is "field" 362 | attributeId, 363 | children 364 | }; 365 | 366 | // Cast to TanaNode to satisfy the type system 367 | const result = await this.tanaClient.createNode(targetNodeId, fieldNode as any); 368 | 369 | return { 370 | content: [ 371 | { 372 | type: 'text', 373 | text: JSON.stringify(result, null, 2) 374 | } 375 | ], 376 | isError: false 377 | }; 378 | } catch (error) { 379 | return { 380 | content: [ 381 | { 382 | type: 'text', 383 | text: `Error creating field node: ${error instanceof Error ? error.message : String(error)}` 384 | } 385 | ], 386 | isError: true 387 | }; 388 | } 389 | } 390 | ); 391 | 392 | // Set node name tool 393 | this.server.tool( 394 | 'set_node_name', 395 | { 396 | nodeId: z.string(), 397 | newName: z.string() 398 | }, 399 | async ({ nodeId, newName }) => { 400 | try { 401 | const result = await this.tanaClient.setNodeName(nodeId, newName); 402 | 403 | return { 404 | content: [ 405 | { 406 | type: 'text', 407 | text: JSON.stringify(result, null, 2) 408 | } 409 | ], 410 | isError: false 411 | }; 412 | } catch (error) { 413 | return { 414 | content: [ 415 | { 416 | type: 'text', 417 | text: `Error setting node name: ${error instanceof Error ? error.message : String(error)}` 418 | } 419 | ], 420 | isError: true 421 | }; 422 | } 423 | } 424 | ); 425 | 426 | // Create a complex node structure tool 427 | this.server.tool( 428 | 'create_node_structure', 429 | { 430 | targetNodeId: z.string().optional(), 431 | node: NodeSchema 432 | }, 433 | async ({ targetNodeId, node }) => { 434 | try { 435 | // Cast to TanaNode to satisfy the type system 436 | const result = await this.tanaClient.createNode(targetNodeId, node as any); 437 | 438 | return { 439 | content: [ 440 | { 441 | type: 'text', 442 | text: JSON.stringify(result, null, 2) 443 | } 444 | ], 445 | isError: false 446 | }; 447 | } catch (error) { 448 | return { 449 | content: [ 450 | { 451 | type: 'text', 452 | text: `Error creating node structure: ${error instanceof Error ? error.message : String(error)}` 453 | } 454 | ], 455 | isError: true 456 | }; 457 | } 458 | } 459 | ); 460 | 461 | // Create a supertag tool 462 | this.server.tool( 463 | 'create_supertag', 464 | { 465 | targetNodeId: z.string().optional().default('SCHEMA'), 466 | name: z.string(), 467 | description: z.string().optional() 468 | }, 469 | async ({ targetNodeId, name, description }) => { 470 | try { 471 | const node: TanaPlainNode = { 472 | name, 473 | description, 474 | supertags: [{ id: 'SYS_T01' }] 475 | }; 476 | 477 | const result = await this.tanaClient.createNode(targetNodeId, node); 478 | return { 479 | content: [ 480 | { 481 | type: 'text', 482 | text: JSON.stringify(result, null, 2) 483 | } 484 | ], 485 | isError: false 486 | }; 487 | } catch (error) { 488 | return { 489 | content: [ 490 | { 491 | type: 'text', 492 | text: `Error creating supertag: ${error instanceof Error ? error.message : String(error)}` 493 | } 494 | ], 495 | isError: true 496 | }; 497 | } 498 | } 499 | ); 500 | 501 | // Create a field tool 502 | this.server.tool( 503 | 'create_field', 504 | { 505 | targetNodeId: z.string().optional().default('SCHEMA'), 506 | name: z.string(), 507 | description: z.string().optional() 508 | }, 509 | async ({ targetNodeId, name, description }) => { 510 | try { 511 | const node: TanaPlainNode = { 512 | name, 513 | description, 514 | supertags: [{ id: 'SYS_T02' }] 515 | }; 516 | 517 | const result = await this.tanaClient.createNode(targetNodeId, node); 518 | return { 519 | content: [ 520 | { 521 | type: 'text', 522 | text: JSON.stringify(result, null, 2) 523 | } 524 | ], 525 | isError: false 526 | }; 527 | } catch (error) { 528 | return { 529 | content: [ 530 | { 531 | type: 'text', 532 | text: `Error creating field: ${error instanceof Error ? error.message : String(error)}` 533 | } 534 | ], 535 | isError: true 536 | }; 537 | } 538 | } 539 | ); 540 | } 541 | 542 | /** 543 | * Register prompts for common Tana operations 544 | */ 545 | private registerPrompts(): void { 546 | // Prompt for creating a task 547 | this.server.prompt( 548 | 'create-task', 549 | 'Create a task node in Tana with optional due date and tags', 550 | { 551 | title: z.string().describe('Task title'), 552 | description: z.string().optional().describe('Task description'), 553 | dueDate: z.string().optional().describe('Due date in ISO format (YYYY-MM-DD)'), 554 | priority: z.enum(['high', 'medium', 'low']).optional().describe('Task priority'), 555 | tags: z.string().optional().describe('Comma-separated tags to apply to the task') 556 | }, 557 | ({ title, description, dueDate, priority, tags }) => { 558 | const parts = [`Create a task in Tana: "${title}"`]; 559 | 560 | if (description) parts.push(`Description: ${description}`); 561 | if (dueDate) parts.push(`Due date: ${dueDate}`); 562 | if (priority) parts.push(`Priority: ${priority}`); 563 | if (tags) parts.push(`Tags: ${tags}`); 564 | 565 | return { 566 | messages: [{ 567 | role: 'user', 568 | content: { 569 | type: 'text', 570 | text: parts.join('\n') 571 | } 572 | }] 573 | }; 574 | } 575 | ); 576 | 577 | // Prompt for creating a project 578 | this.server.prompt( 579 | 'create-project', 580 | 'Create a project structure in Tana with goals and milestones', 581 | { 582 | name: z.string().describe('Project name'), 583 | description: z.string().optional().describe('Project description'), 584 | goals: z.string().optional().describe('Comma-separated list of project goals'), 585 | startDate: z.string().optional().describe('Start date in ISO format'), 586 | endDate: z.string().optional().describe('End date in ISO format'), 587 | team: z.string().optional().describe('Comma-separated team member names') 588 | }, 589 | ({ name, description, goals, startDate, endDate, team }) => { 590 | const parts = [`Create a project in Tana: "${name}"`]; 591 | 592 | if (description) parts.push(`Description: ${description}`); 593 | if (goals) { 594 | parts.push('Goals:'); 595 | const goalList = goals.split(',').map(g => g.trim()); 596 | goalList.forEach(goal => parts.push(`- ${goal}`)); 597 | } 598 | if (startDate) parts.push(`Start date: ${startDate}`); 599 | if (endDate) parts.push(`End date: ${endDate}`); 600 | if (team) parts.push(`Team: ${team}`); 601 | 602 | return { 603 | messages: [{ 604 | role: 'user', 605 | content: { 606 | type: 'text', 607 | text: parts.join('\n') 608 | } 609 | }] 610 | }; 611 | } 612 | ); 613 | 614 | // Prompt for creating meeting notes 615 | this.server.prompt( 616 | 'create-meeting-notes', 617 | 'Create structured meeting notes in Tana', 618 | { 619 | title: z.string().describe('Meeting title'), 620 | date: z.string().describe('Meeting date in ISO format'), 621 | attendees: z.string().describe('Comma-separated list of attendees'), 622 | agenda: z.string().optional().describe('Comma-separated meeting agenda items'), 623 | notes: z.string().optional().describe('Meeting notes'), 624 | actionItems: z.string().optional().describe('Action items as JSON string array with task, assignee, and dueDate fields') 625 | }, 626 | ({ title, date, attendees, agenda, notes, actionItems }) => { 627 | const parts = [ 628 | `Create meeting notes in Tana:`, 629 | `Title: ${title}`, 630 | `Date: ${date}`, 631 | `Attendees: ${attendees}` 632 | ]; 633 | 634 | if (agenda) { 635 | parts.push('\nAgenda:'); 636 | const agendaItems = agenda.split(',').map(a => a.trim()); 637 | agendaItems.forEach(item => parts.push(`- ${item}`)); 638 | } 639 | 640 | if (notes) { 641 | parts.push(`\nNotes:\n${notes}`); 642 | } 643 | 644 | if (actionItems) { 645 | parts.push('\nAction Items:'); 646 | try { 647 | const items = JSON.parse(actionItems); 648 | if (Array.isArray(items)) { 649 | items.forEach((item: any) => { 650 | let actionText = `- ${item.task}`; 651 | if (item.assignee) actionText += ` (assigned to: ${item.assignee})`; 652 | if (item.dueDate) actionText += ` [due: ${item.dueDate}]`; 653 | parts.push(actionText); 654 | }); 655 | } 656 | } catch (e) { 657 | parts.push(`- ${actionItems}`); 658 | } 659 | } 660 | 661 | return { 662 | messages: [{ 663 | role: 'user', 664 | content: { 665 | type: 'text', 666 | text: parts.join('\n') 667 | } 668 | }] 669 | }; 670 | } 671 | ); 672 | 673 | // Prompt for knowledge base entry 674 | this.server.prompt( 675 | 'create-knowledge-entry', 676 | 'Create a knowledge base entry in Tana', 677 | { 678 | topic: z.string().describe('Topic or title'), 679 | category: z.string().optional().describe('Category or type'), 680 | content: z.string().describe('Main content'), 681 | sources: z.string().optional().describe('Comma-separated reference sources or links'), 682 | relatedTopics: z.string().optional().describe('Comma-separated related topics for linking') 683 | }, 684 | ({ topic, category, content, sources, relatedTopics }) => { 685 | const parts = [`Create a knowledge entry in Tana about: "${topic}"`]; 686 | 687 | if (category) parts.push(`Category: ${category}`); 688 | parts.push(`\nContent:\n${content}`); 689 | 690 | if (sources) { 691 | parts.push('\nSources:'); 692 | const sourceList = sources.split(',').map(s => s.trim()); 693 | sourceList.forEach(source => parts.push(`- ${source}`)); 694 | } 695 | 696 | if (relatedTopics) { 697 | parts.push(`\nRelated topics: ${relatedTopics}`); 698 | } 699 | 700 | return { 701 | messages: [{ 702 | role: 'user', 703 | content: { 704 | type: 'text', 705 | text: parts.join('\n') 706 | } 707 | }] 708 | }; 709 | } 710 | ); 711 | } 712 | 713 | /** 714 | * Register resources for interacting with Tana 715 | */ 716 | private registerResources(): void { 717 | // API documentation resource 718 | this.server.resource( 719 | 'api-docs', 720 | 'tana://api/documentation', 721 | { 722 | name: 'Tana API Documentation', 723 | description: 'Overview of Tana Input API capabilities and usage', 724 | mimeType: 'text/markdown' 725 | }, 726 | async () => ({ 727 | contents: [{ 728 | uri: 'tana://api/documentation', 729 | mimeType: 'text/markdown', 730 | text: `# Tana Input API Documentation 731 | 732 | ## Overview 733 | This MCP server provides access to Tana's Input API, allowing you to create and manipulate nodes in your Tana workspace. 734 | 735 | ## Available Node Types 736 | - **Plain nodes**: Basic text nodes with optional formatting 737 | - **Reference nodes**: Links to existing nodes by ID 738 | - **Date nodes**: Nodes representing dates 739 | - **URL nodes**: Web links with metadata 740 | - **Checkbox nodes**: Boolean/task nodes 741 | - **File nodes**: Attachments with base64 encoded data 742 | - **Field nodes**: Structured data fields 743 | 744 | ## Tools Available 745 | 746 | ### Basic Node Creation 747 | - \`create_plain_node\`: Create a simple text node 748 | - \`create_reference_node\`: Create a reference to another node 749 | - \`create_date_node\`: Create a date node 750 | - \`create_url_node\`: Create a URL node 751 | - \`create_checkbox_node\`: Create a checkbox/task node 752 | - \`create_file_node\`: Create a file attachment node 753 | 754 | ### Advanced Features 755 | - \`create_field_node\`: Create structured field nodes 756 | - \`create_node_structure\`: Create complex nested node structures 757 | - \`set_node_name\`: Update the name of an existing node 758 | 759 | ### Schema Management 760 | - \`create_supertag\`: Create new supertags (node types) 761 | - \`create_field\`: Create new field definitions 762 | 763 | ## API Limits 764 | - Maximum 100 nodes per request 765 | - 1 request per second per token 766 | - Payload limit: 5000 characters 767 | - Workspace limit: 750k nodes` 768 | }] 769 | }) 770 | ); 771 | 772 | // Node types reference 773 | this.server.resource( 774 | 'node-types', 775 | 'tana://reference/node-types', 776 | { 777 | name: 'Tana Node Types Reference', 778 | description: 'Detailed information about all supported node types', 779 | mimeType: 'text/markdown' 780 | }, 781 | async () => ({ 782 | contents: [{ 783 | uri: 'tana://reference/node-types', 784 | mimeType: 'text/markdown', 785 | text: `# Tana Node Types Reference 786 | 787 | ## Plain Node 788 | The most basic node type, used for text content. 789 | \`\`\`json 790 | { 791 | "name": "Node title", 792 | "description": "Node content with **formatting**", 793 | "supertags": [{"id": "tagId"}], 794 | "children": [] 795 | } 796 | \`\`\` 797 | 798 | ## Reference Node 799 | Links to an existing node. 800 | \`\`\`json 801 | { 802 | "dataType": "reference", 803 | "id": "targetNodeId" 804 | } 805 | \`\`\` 806 | 807 | ## Date Node 808 | Represents a date value. 809 | \`\`\`json 810 | { 811 | "dataType": "date", 812 | "name": "2024-01-01", 813 | "description": "Optional description" 814 | } 815 | \`\`\` 816 | 817 | ## URL Node 818 | Stores web links. 819 | \`\`\`json 820 | { 821 | "dataType": "url", 822 | "name": "https://example.com", 823 | "description": "Link description" 824 | } 825 | \`\`\` 826 | 827 | ## Checkbox Node 828 | Boolean/task nodes. 829 | \`\`\`json 830 | { 831 | "dataType": "boolean", 832 | "name": "Task name", 833 | "value": false 834 | } 835 | \`\`\` 836 | 837 | ## File Node 838 | File attachments. 839 | \`\`\`json 840 | { 841 | "dataType": "file", 842 | "file": "base64EncodedData", 843 | "filename": "document.pdf", 844 | "contentType": "application/pdf" 845 | } 846 | \`\`\` 847 | 848 | ## Field Node 849 | Structured data fields. 850 | \`\`\`json 851 | { 852 | "type": "field", 853 | "attributeId": "fieldId", 854 | "children": [/* nested nodes */] 855 | } 856 | \`\`\`` 857 | }] 858 | }) 859 | ); 860 | 861 | // Examples resource 862 | this.server.resource( 863 | 'examples', 864 | 'tana://examples/common-patterns', 865 | { 866 | name: 'Common Usage Examples', 867 | description: 'Example patterns for common Tana operations', 868 | mimeType: 'text/markdown' 869 | }, 870 | async () => ({ 871 | contents: [{ 872 | uri: 'tana://examples/common-patterns', 873 | mimeType: 'text/markdown', 874 | text: `# Common Tana Usage Examples 875 | 876 | ## Creating a Task with Due Date 877 | \`\`\`javascript 878 | // Use create_checkbox_node with a child date node 879 | { 880 | "name": "Complete project proposal", 881 | "checked": false, 882 | "children": [ 883 | { 884 | "type": "field", 885 | "attributeId": "SYS_A13", // Due date field 886 | "children": [{ 887 | "dataType": "date", 888 | "name": "2024-12-31" 889 | }] 890 | } 891 | ] 892 | } 893 | \`\`\` 894 | 895 | ## Creating a Project Structure 896 | \`\`\`javascript 897 | // Use create_node_structure for complex hierarchies 898 | { 899 | "name": "Q1 2024 Product Launch", 900 | "supertags": [{"id": "projectTagId"}], 901 | "children": [ 902 | { 903 | "name": "Milestones", 904 | "children": [ 905 | {"name": "Design Complete", "dataType": "date", "name": "2024-01-15"}, 906 | {"name": "Development Done", "dataType": "date", "name": "2024-02-28"} 907 | ] 908 | }, 909 | { 910 | "name": "Tasks", 911 | "children": [ 912 | {"dataType": "boolean", "name": "Create mockups", "value": false}, 913 | {"dataType": "boolean", "name": "Write documentation", "value": false} 914 | ] 915 | } 916 | ] 917 | } 918 | \`\`\` 919 | 920 | ## Adding Multiple Tags 921 | \`\`\`javascript 922 | { 923 | "name": "Important Note", 924 | "supertags": [ 925 | {"id": "tag1Id"}, 926 | {"id": "tag2Id", "fields": {"priority": "high"}} 927 | ] 928 | } 929 | \`\`\`` 930 | }] 931 | }) 932 | ); 933 | 934 | // Server info resource 935 | this.server.resource( 936 | 'server-info', 937 | 'tana://info', 938 | { 939 | name: 'Server Information', 940 | description: 'Current server status and configuration', 941 | mimeType: 'text/plain' 942 | }, 943 | async () => ({ 944 | contents: [{ 945 | uri: 'tana://info', 946 | mimeType: 'text/plain', 947 | text: `Tana MCP Server v1.2.0 948 | Status: Connected 949 | API Endpoint: ${this.tanaClient['endpoint']} 950 | 951 | Capabilities: 952 | - Tools: Multiple tools available for node creation and management 953 | - Prompts: Pre-configured prompts for common tasks 954 | - Resources: Documentation and examples available 955 | 956 | For detailed API documentation, see the 'api-docs' resource. 957 | For examples, see the 'examples' resource.` 958 | }] 959 | }) 960 | ); 961 | } 962 | 963 | } --------------------------------------------------------------------------------