feat: add SolidTime MCP server for time tracking integration
Implements a Model Context Protocol server that exposes SolidTime's time tracking API as 22+ tools for use with Claude, Cursor, and other MCP-compatible clients. Supports stdio and HTTP transport modes, Docker deployment, and self-hosted SolidTime instances. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
src/tools/organizations.ts
Normal file
80
src/tools/organizations.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import { ApiClient } from "../api-client.js";
|
||||
import { API_PATHS } from "../constants.js";
|
||||
import { formatCurrency } from "../formatting.js";
|
||||
import type { Organization } from "../types.js";
|
||||
|
||||
export function registerOrganizationTools(server: McpServer, api: ApiClient, orgId: string) {
|
||||
server.registerTool(
|
||||
"solidtime_get_organization",
|
||||
{
|
||||
title: "Get Organization",
|
||||
description: "Get the current organization's details including name, currency, and billable rate.",
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
const result = await api.get<{ data: Organization }>(API_PATHS.organization(orgId));
|
||||
return {
|
||||
content: [{ type: "text", text: formatOrganization(result.data) }],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"solidtime_update_organization",
|
||||
{
|
||||
title: "Update Organization",
|
||||
description: "Update the organization's name, currency, or billable rate.",
|
||||
inputSchema: {
|
||||
name: z.string().min(1).optional().describe("New organization name"),
|
||||
currency: z.string().min(3).max(3).optional().describe("New currency code (e.g. EUR, USD)"),
|
||||
billable_rate: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe("New default billable rate in cents (e.g. 15000 = EUR 150.00)"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (params.name !== undefined) body.name = params.name;
|
||||
if (params.currency !== undefined) body.currency = params.currency;
|
||||
if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate;
|
||||
|
||||
const result = await api.put<{ data: Organization }>(
|
||||
API_PATHS.organization(orgId),
|
||||
body
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `Organization updated.\n\n${formatOrganization(result.data)}` },
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function formatOrganization(org: Organization): string {
|
||||
const parts = [
|
||||
`ID: ${org.id}`,
|
||||
`Name: ${org.name}`,
|
||||
`Currency: ${org.currency}`,
|
||||
];
|
||||
if (org.billable_rate !== null) {
|
||||
parts.push(`Default Billable Rate: ${formatCurrency(org.billable_rate)}/h`);
|
||||
}
|
||||
return parts.join("\n");
|
||||
}
|
||||
Reference in New Issue
Block a user