@slyswiss/infinity-mcp 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +101 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/attributes.d.ts +3 -0
- package/dist/tools/attributes.js +9 -0
- package/dist/tools/attributes.js.map +1 -0
- package/dist/tools/boards.d.ts +3 -0
- package/dist/tools/boards.js +10 -0
- package/dist/tools/boards.js.map +1 -0
- package/dist/tools/comments.d.ts +3 -0
- package/dist/tools/comments.js +32 -0
- package/dist/tools/comments.js.map +1 -0
- package/dist/tools/folders.d.ts +3 -0
- package/dist/tools/folders.js +9 -0
- package/dist/tools/folders.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/items.d.ts +3 -0
- package/dist/tools/items.js +93 -0
- package/dist/tools/items.js.map +1 -0
- package/dist/tools/util.d.ts +10 -0
- package/dist/tools/util.js +16 -0
- package/dist/tools/util.js.map +1 -0
- package/dist/tools/workspaces.d.ts +3 -0
- package/dist/tools/workspaces.js +6 -0
- package/dist/tools/workspaces.js.map +1 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sly Swiss
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @slyswiss/infinity-mcp
|
|
2
|
+
|
|
3
|
+
> Community [Model Context Protocol](https://modelcontextprotocol.io) server for [Infinity](https://startinfinity.com) — lets Claude (and other MCP clients) read and manage your Infinity tickets, comments, and boards.
|
|
4
|
+
>
|
|
5
|
+
> **Not affiliated with Infinity Innovations Inc.** Built against Infinity's public REST API v2.1.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Exposes 12 tools that map to Infinity's REST API, scoped around day-to-day ticket workflows:
|
|
10
|
+
|
|
11
|
+
**Discovery**
|
|
12
|
+
- `list_workspaces` — find your workspace ids
|
|
13
|
+
- `list_boards` — find your board ids
|
|
14
|
+
- `list_folders` — list folders (often used as stages/columns)
|
|
15
|
+
- `list_attributes` — list a board's fields (name, status, assignee, etc.) and their types
|
|
16
|
+
- `get_profile` — who is the token owner ("me")
|
|
17
|
+
|
|
18
|
+
**Tickets**
|
|
19
|
+
- `list_items` — list tickets, optionally filtered by folder, with cursor pagination
|
|
20
|
+
- `get_item` — fetch one ticket with its values
|
|
21
|
+
- `create_item` — create a ticket in a folder, with attribute values
|
|
22
|
+
- `update_item` — move between folders, change status, assign, edit fields (one tool, three jobs)
|
|
23
|
+
- `delete_item` — soft-delete a ticket
|
|
24
|
+
|
|
25
|
+
**Comments**
|
|
26
|
+
- `list_comments` — list comments on a ticket
|
|
27
|
+
- `create_comment` — post a comment (HTML supported)
|
|
28
|
+
|
|
29
|
+
## Install + configure
|
|
30
|
+
|
|
31
|
+
### 1. Get a personal access token
|
|
32
|
+
|
|
33
|
+
In Infinity:
|
|
34
|
+
1. Click your profile → **Enable Developer Features** (one-time toggle).
|
|
35
|
+
2. Sidebar → **Developer → Tokens → Create Token**.
|
|
36
|
+
3. Copy the token. Treat it like a password.
|
|
37
|
+
|
|
38
|
+
### 2. Wire into your MCP client
|
|
39
|
+
|
|
40
|
+
#### Claude Desktop
|
|
41
|
+
|
|
42
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"infinity": {
|
|
48
|
+
"command": "npx",
|
|
49
|
+
"args": ["-y", "@slyswiss/infinity-mcp"],
|
|
50
|
+
"env": {
|
|
51
|
+
"INFINITY_API_TOKEN": "your_token_here"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Restart Claude Desktop.
|
|
59
|
+
|
|
60
|
+
#### Claude Code
|
|
61
|
+
|
|
62
|
+
In your project's `.mcp.json` (or `~/.claude.json`):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"infinity": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@slyswiss/infinity-mcp"],
|
|
70
|
+
"env": {
|
|
71
|
+
"INFINITY_API_TOKEN": "your_token_here"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environment variables
|
|
79
|
+
|
|
80
|
+
| Variable | Required | Description |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `INFINITY_API_TOKEN` | yes | Personal access token from the Infinity developer settings. |
|
|
83
|
+
| `INFINITY_BASE_URL` | no | Override the API base URL. Defaults to `https://app.startinfinity.com/api/v2`. |
|
|
84
|
+
|
|
85
|
+
## Example prompts (once configured)
|
|
86
|
+
|
|
87
|
+
- *"List my Infinity workspaces."*
|
|
88
|
+
- *"Show me the tickets in the 'In Progress' folder on the Engineering board."*
|
|
89
|
+
- *"Create a ticket titled 'Fix login bug' in the Backlog folder."*
|
|
90
|
+
- *"Move ticket abc-123 to Done."*
|
|
91
|
+
- *"Add a comment to ticket abc-123 saying 'shipped in v1.4'."*
|
|
92
|
+
|
|
93
|
+
Claude will call `list_workspaces` / `list_boards` / `list_attributes` as needed to discover ids before mutating anything.
|
|
94
|
+
|
|
95
|
+
## Limitations / not yet in scope
|
|
96
|
+
|
|
97
|
+
- **No attribute CRUD** — you can't create or rename board columns from here. Configure your board in Infinity, then use this for tickets.
|
|
98
|
+
- **No board creation** — same reasoning.
|
|
99
|
+
- **No file upload attachments** — attachment-from-URL and file-upload endpoints aren't wrapped yet.
|
|
100
|
+
- **No webhooks** — webhook config is out of scope for an interactive assistant.
|
|
101
|
+
- **No server-side attribute-value filtering** — the Infinity API doesn't support it. To find items by status/assignee, the tool expands values and the model filters client-side.
|
|
102
|
+
- **Workflow shortcuts (`my_tickets`, `move_to_done`, etc.)** are deliberately not exposed yet. They'd encode team-specific stage names — we'd rather Claude discover the schema explicitly per board.
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://gitlab.com/sly-swiss/infinity-mcp.git
|
|
108
|
+
cd infinity-mcp
|
|
109
|
+
npm install
|
|
110
|
+
npm run build
|
|
111
|
+
INFINITY_API_TOKEN=xxx node dist/index.js
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Commit conventions
|
|
115
|
+
|
|
116
|
+
This repo uses [semantic-release](https://semantic-release.gitbook.io/). Commit prefixes drive versioning:
|
|
117
|
+
|
|
118
|
+
- `fix:` → patch release
|
|
119
|
+
- `feat:` → minor release
|
|
120
|
+
- `BREAKING CHANGE:` in body or `feat!:` → major release
|
|
121
|
+
- `chore:` / `docs:` / `refactor:` / `style:` / `test:` → no release
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
[MIT](LICENSE)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface InfinityClientOptions {
|
|
2
|
+
token: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
userAgent?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RequestOptions {
|
|
7
|
+
query?: Record<string, unknown>;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare class InfinityApiError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly statusText: string;
|
|
13
|
+
readonly body?: unknown | undefined;
|
|
14
|
+
constructor(status: number, statusText: string, message: string, body?: unknown | undefined);
|
|
15
|
+
}
|
|
16
|
+
export declare class InfinityClient {
|
|
17
|
+
private readonly token;
|
|
18
|
+
private readonly baseUrl;
|
|
19
|
+
private readonly userAgent;
|
|
20
|
+
constructor(opts: InfinityClientOptions);
|
|
21
|
+
request<T = unknown>(method: string, path: string, { query, body }?: RequestOptions): Promise<T>;
|
|
22
|
+
get<T = unknown>(path: string, query?: Record<string, unknown>): Promise<T>;
|
|
23
|
+
post<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
24
|
+
put<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
25
|
+
delete<T = unknown>(path: string): Promise<T>;
|
|
26
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://app.startinfinity.com/api/v2";
|
|
2
|
+
export class InfinityApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
statusText;
|
|
5
|
+
body;
|
|
6
|
+
constructor(status, statusText, message, body) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.statusText = statusText;
|
|
10
|
+
this.body = body;
|
|
11
|
+
this.name = "InfinityApiError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class InfinityClient {
|
|
15
|
+
token;
|
|
16
|
+
baseUrl;
|
|
17
|
+
userAgent;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
if (!opts.token) {
|
|
20
|
+
throw new Error("Infinity API token is required");
|
|
21
|
+
}
|
|
22
|
+
this.token = opts.token;
|
|
23
|
+
this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
|
|
24
|
+
this.userAgent = opts.userAgent ?? "infinity-mcp";
|
|
25
|
+
}
|
|
26
|
+
async request(method, path, { query, body } = {}) {
|
|
27
|
+
const url = new URL(this.baseUrl + path);
|
|
28
|
+
if (query) {
|
|
29
|
+
for (const [key, value] of Object.entries(query)) {
|
|
30
|
+
if (value === undefined || value === null)
|
|
31
|
+
continue;
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
for (const v of value) {
|
|
34
|
+
url.searchParams.append(`${key}[]`, String(v));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
url.searchParams.set(key, String(value));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const headers = {
|
|
43
|
+
Authorization: `Bearer ${this.token}`,
|
|
44
|
+
Accept: "application/json",
|
|
45
|
+
"User-Agent": this.userAgent,
|
|
46
|
+
};
|
|
47
|
+
let bodyStr;
|
|
48
|
+
if (body !== undefined) {
|
|
49
|
+
headers["Content-Type"] = "application/json";
|
|
50
|
+
bodyStr = JSON.stringify(body);
|
|
51
|
+
}
|
|
52
|
+
const res = await fetch(url, { method, headers, body: bodyStr });
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
const text = await res.text();
|
|
55
|
+
let parsed;
|
|
56
|
+
let detail = text;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(text);
|
|
59
|
+
if (parsed &&
|
|
60
|
+
typeof parsed === "object" &&
|
|
61
|
+
"message" in parsed &&
|
|
62
|
+
typeof parsed.message === "string") {
|
|
63
|
+
detail = parsed.message;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// non-JSON error body
|
|
68
|
+
}
|
|
69
|
+
let message = `Infinity API ${res.status} ${res.statusText}`;
|
|
70
|
+
if (detail)
|
|
71
|
+
message += `: ${detail}`;
|
|
72
|
+
if (res.status === 429) {
|
|
73
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
74
|
+
message += retryAfter
|
|
75
|
+
? ` (retry after ${retryAfter}s)`
|
|
76
|
+
: " (rate limited; default limit is 180 requests/minute)";
|
|
77
|
+
}
|
|
78
|
+
throw new InfinityApiError(res.status, res.statusText, message, parsed ?? text);
|
|
79
|
+
}
|
|
80
|
+
if (res.status === 204)
|
|
81
|
+
return undefined;
|
|
82
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
83
|
+
if (!contentType.includes("application/json")) {
|
|
84
|
+
return (await res.text());
|
|
85
|
+
}
|
|
86
|
+
return (await res.json());
|
|
87
|
+
}
|
|
88
|
+
get(path, query) {
|
|
89
|
+
return this.request("GET", path, { query });
|
|
90
|
+
}
|
|
91
|
+
post(path, body) {
|
|
92
|
+
return this.request("POST", path, { body });
|
|
93
|
+
}
|
|
94
|
+
put(path, body) {
|
|
95
|
+
return this.request("PUT", path, { body });
|
|
96
|
+
}
|
|
97
|
+
delete(path) {
|
|
98
|
+
return this.request("DELETE", path);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { InfinityClient } from "./client.js";
|
|
5
|
+
import { registerAllTools } from "./tools/index.js";
|
|
6
|
+
const PKG_NAME = "@slyswiss/infinity-mcp";
|
|
7
|
+
const PKG_VERSION = "0.1.0";
|
|
8
|
+
async function main() {
|
|
9
|
+
const token = process.env.INFINITY_API_TOKEN;
|
|
10
|
+
if (!token) {
|
|
11
|
+
console.error("[infinity-mcp] Missing INFINITY_API_TOKEN environment variable.\n" +
|
|
12
|
+
"Generate a personal access token at: Infinity → Profile → Enable Developer Features → Developer → Tokens.\n" +
|
|
13
|
+
"Then set INFINITY_API_TOKEN in your MCP client config.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const baseUrl = process.env.INFINITY_BASE_URL || undefined;
|
|
17
|
+
const client = new InfinityClient({
|
|
18
|
+
token,
|
|
19
|
+
baseUrl,
|
|
20
|
+
userAgent: `${PKG_NAME}/${PKG_VERSION}`,
|
|
21
|
+
});
|
|
22
|
+
const server = new McpServer({
|
|
23
|
+
name: PKG_NAME,
|
|
24
|
+
version: PKG_VERSION,
|
|
25
|
+
});
|
|
26
|
+
registerAllTools(server, client);
|
|
27
|
+
const transport = new StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
console.error(`[infinity-mcp] ${PKG_NAME} v${PKG_VERSION} listening on stdio`);
|
|
30
|
+
}
|
|
31
|
+
main().catch((err) => {
|
|
32
|
+
console.error("[infinity-mcp] fatal:", err);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,QAAQ,GAAG,wBAAwB,CAAC;AAC1C,MAAM,WAAW,GAAG,OAAO,CAAC;AAE5B,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX,mEAAmE;YACjE,6GAA6G;YAC7G,wDAAwD,CAC3D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,SAAS,CAAC;IAE3D,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;QAChC,KAAK;QACL,OAAO;QACP,SAAS,EAAE,GAAG,QAAQ,IAAI,WAAW,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,WAAW;KACrB,CAAC,CAAC;IAEH,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,kBAAkB,QAAQ,KAAK,WAAW,qBAAqB,CAAC,CAAC;AACjF,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./util.js";
|
|
3
|
+
export function registerAttributeTools(server, client) {
|
|
4
|
+
server.tool("list_attributes", "List the attributes (columns/fields) defined on a board. Each attribute has an id, name, and type (e.g. 'text', 'label', 'members', 'date', 'number', 'links'). For 'label' type attributes, settings.labels contains the available option values (with their own ids). Call this before create_item or update_item so you know which attribute_id to set, and what shape the `data` field takes for each type — for example, a 'label' attribute expects an array of label ids in `data`, while 'text' expects a string.", {
|
|
5
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
6
|
+
board_id: z.string().describe("The board id."),
|
|
7
|
+
}, ({ workspace_id, board_id }) => run(() => client.get(`/workspaces/${workspace_id}/boards/${board_id}/attributes`)));
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=attributes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributes.js","sourceRoot":"","sources":["../../src/tools/attributes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,MAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,2fAA2f,EAC3f;QACE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACtD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;KAC/C,EACD,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,EAAE,CAC7B,GAAG,CAAC,GAAG,EAAE,CACP,MAAM,CAAC,GAAG,CAAC,eAAe,YAAY,WAAW,QAAQ,aAAa,CAAC,CACxE,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./util.js";
|
|
3
|
+
export function registerBoardTools(server, client) {
|
|
4
|
+
server.tool("list_boards", "List all boards in a workspace. A board is the top-level container for tickets/items. Use list_workspaces first if you don't have the workspace_id.", {
|
|
5
|
+
workspace_id: z
|
|
6
|
+
.string()
|
|
7
|
+
.describe("The workspace id (from list_workspaces)."),
|
|
8
|
+
}, ({ workspace_id }) => run(() => client.get(`/workspaces/${workspace_id}/boards`)));
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=boards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boards.js","sourceRoot":"","sources":["../../src/tools/boards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,UAAU,kBAAkB,CAChC,MAAiB,EACjB,MAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,aAAa,EACb,qJAAqJ,EACrJ;QACE,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,CAAC,0CAA0C,CAAC;KACxD,EACD,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CACnB,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,YAAY,SAAS,CAAC,CAAC,CAC9D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./util.js";
|
|
3
|
+
export function registerCommentTools(server, client) {
|
|
4
|
+
server.tool("list_comments", "List comments on a ticket. Comments are threaded — top-level comments have parent_id=null, replies have parent_id set to the parent comment's id.", {
|
|
5
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
6
|
+
board_id: z.string().describe("The board id."),
|
|
7
|
+
item_id: z.string().describe("The item (ticket) id."),
|
|
8
|
+
limit: z
|
|
9
|
+
.number()
|
|
10
|
+
.int()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(100)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Max comments to return (1-100)."),
|
|
15
|
+
after: z.string().optional().describe("Pagination cursor."),
|
|
16
|
+
before: z.string().optional().describe("Pagination cursor."),
|
|
17
|
+
}, ({ workspace_id, board_id, item_id, ...query }) => run(() => client.get(`/workspaces/${workspace_id}/boards/${board_id}/items/${item_id}/comments`, query)));
|
|
18
|
+
server.tool("create_comment", "Add a comment to a ticket. The `text` field accepts HTML — for plain text, just pass the string and it will be rendered as-is. Wrap paragraphs in <p>...</p> if you want explicit paragraph breaks. To reply to an existing comment, set parent_id to that comment's id.", {
|
|
19
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
20
|
+
board_id: z.string().describe("The board id."),
|
|
21
|
+
item_id: z.string().describe("The item (ticket) id."),
|
|
22
|
+
text: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.describe("Comment body. HTML is supported (e.g. <p>, <b>, <a href>). Plain text also works."),
|
|
26
|
+
parent_id: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Optional parent comment id, to post this as a reply."),
|
|
30
|
+
}, ({ workspace_id, board_id, item_id, ...body }) => run(() => client.post(`/workspaces/${workspace_id}/boards/${board_id}/items/${item_id}/comments`, body)));
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","sourceRoot":"","sources":["../../src/tools/comments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,MAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,eAAe,EACf,mJAAmJ,EACnJ;QACE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACtD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC9C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACrD,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,iCAAiC,CAAC;QAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAC3D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;KAC7D,EACD,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAChD,GAAG,CAAC,GAAG,EAAE,CACP,MAAM,CAAC,GAAG,CACR,eAAe,YAAY,WAAW,QAAQ,UAAU,OAAO,WAAW,EAC1E,KAAK,CACN,CACF,CACJ,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,0QAA0Q,EAC1Q;QACE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACtD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC9C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACrD,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CACP,mFAAmF,CACpF;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,sDAAsD,CAAC;KACpE,EACD,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAC/C,GAAG,CAAC,GAAG,EAAE,CACP,MAAM,CAAC,IAAI,CACT,eAAe,YAAY,WAAW,QAAQ,UAAU,OAAO,WAAW,EAC1E,IAAI,CACL,CACF,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./util.js";
|
|
3
|
+
export function registerFolderTools(server, client) {
|
|
4
|
+
server.tool("list_folders", "List folders in a board. In many Infinity boards, folders represent workflow stages (e.g. 'Backlog', 'In Progress', 'Done'). To move a ticket between stages-as-folders, call update_item with a new folder_id. (If your board models stages as an attribute value instead, use list_attributes to find the status attribute.)", {
|
|
5
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
6
|
+
board_id: z.string().describe("The board id (from list_boards)."),
|
|
7
|
+
}, ({ workspace_id, board_id }) => run(() => client.get(`/workspaces/${workspace_id}/boards/${board_id}/folders`)));
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=folders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/tools/folders.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,UAAU,mBAAmB,CACjC,MAAiB,EACjB,MAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,cAAc,EACd,gUAAgU,EAChU;QACE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACtD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;KAClE,EACD,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,EAAE,CAC7B,GAAG,CAAC,GAAG,EAAE,CACP,MAAM,CAAC,GAAG,CAAC,eAAe,YAAY,WAAW,QAAQ,UAAU,CAAC,CACrE,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { registerWorkspaceTools } from "./workspaces.js";
|
|
2
|
+
import { registerBoardTools } from "./boards.js";
|
|
3
|
+
import { registerFolderTools } from "./folders.js";
|
|
4
|
+
import { registerAttributeTools } from "./attributes.js";
|
|
5
|
+
import { registerItemTools } from "./items.js";
|
|
6
|
+
import { registerCommentTools } from "./comments.js";
|
|
7
|
+
export function registerAllTools(server, client) {
|
|
8
|
+
registerWorkspaceTools(server, client);
|
|
9
|
+
registerBoardTools(server, client);
|
|
10
|
+
registerFolderTools(server, client);
|
|
11
|
+
registerAttributeTools(server, client);
|
|
12
|
+
registerItemTools(server, client);
|
|
13
|
+
registerCommentTools(server, client);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,UAAU,gBAAgB,CAC9B,MAAiB,EACjB,MAAsB;IAEtB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./util.js";
|
|
3
|
+
const valueSchema = z.object({
|
|
4
|
+
attribute_id: z
|
|
5
|
+
.string()
|
|
6
|
+
.describe("The id of the attribute being set (from list_attributes)."),
|
|
7
|
+
data: z
|
|
8
|
+
.unknown()
|
|
9
|
+
.describe("The value for the attribute. Shape depends on the attribute's type — e.g. a string for 'text', an array of label ids for 'label', an ISO date for 'date', an array of user ids for 'members'. Call list_attributes to see settings."),
|
|
10
|
+
});
|
|
11
|
+
const expandOptions = z
|
|
12
|
+
.array(z.enum(["values", "values.attribute", "folder", "created_by"]))
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Which related resources to inline in the response. Pass ['values'] to include attribute values on each item. Pass ['values', 'values.attribute'] to also inline each value's attribute metadata (useful but larger).");
|
|
15
|
+
export function registerItemTools(server, client) {
|
|
16
|
+
server.tool("list_items", "List tickets (items) in a board. Supports filtering by folder, cursor pagination, and optional expansion of related data. Note: the API does NOT support filtering by attribute value server-side — to find items by assignee, status, etc., expand=['values'] and filter client-side. Returns { data: [...], has_more, after, before }.", {
|
|
17
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
18
|
+
board_id: z.string().describe("The board id."),
|
|
19
|
+
folder_id: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional: only return items in this folder. Use this to list tickets in a specific stage when stages are modeled as folders."),
|
|
23
|
+
limit: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(100)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Max items to return (1-100). Default is API-defined."),
|
|
30
|
+
sort_by: z
|
|
31
|
+
.enum(["id", "created_at"])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Sort field."),
|
|
34
|
+
sort_direction: z
|
|
35
|
+
.enum(["asc", "desc"])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Sort direction."),
|
|
38
|
+
after: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Pagination cursor — pass the previous response's `after` value to get the next page."),
|
|
42
|
+
before: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Pagination cursor for the previous page."),
|
|
46
|
+
expand: expandOptions,
|
|
47
|
+
}, ({ workspace_id, board_id, expand, ...query }) => run(() => client.get(`/workspaces/${workspace_id}/boards/${board_id}/items`, { ...query, expand })));
|
|
48
|
+
server.tool("get_item", "Get a single ticket (item) by id, including its folder and values. Use expand=['values', 'values.attribute'] to inline attribute names alongside values — usually the most readable form for showing a ticket's full state.", {
|
|
49
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
50
|
+
board_id: z.string().describe("The board id."),
|
|
51
|
+
item_id: z.string().describe("The item (ticket) id."),
|
|
52
|
+
expand: expandOptions,
|
|
53
|
+
}, ({ workspace_id, board_id, item_id, expand }) => run(() => client.get(`/workspaces/${workspace_id}/boards/${board_id}/items/${item_id}`, { expand })));
|
|
54
|
+
server.tool("create_item", "Create a new ticket (item) in a board. `folder_id` is required — every item lives in a folder. Pass `values` to set attribute fields like name, description, assignee, status, etc. — call list_attributes first to learn which attribute_ids and data shapes to use. The ticket's title in Infinity is just whichever attribute the board uses for that purpose (commonly a 'text' attribute named 'Name'); set it via values.", {
|
|
55
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
56
|
+
board_id: z.string().describe("The board id."),
|
|
57
|
+
folder_id: z
|
|
58
|
+
.string()
|
|
59
|
+
.describe("The folder to create the item in. Use list_folders to find folder ids."),
|
|
60
|
+
values: z
|
|
61
|
+
.array(valueSchema)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Attribute values to set on the new item. Each entry is { attribute_id, data }."),
|
|
64
|
+
parent_id: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Optional parent item id, for creating a sub-item nested under another item."),
|
|
68
|
+
}, ({ workspace_id, board_id, ...body }) => run(() => client.post(`/workspaces/${workspace_id}/boards/${board_id}/items`, body)));
|
|
69
|
+
server.tool("update_item", "Update a ticket. This single tool handles three common operations:\n • MOVE TO STAGE (when stages are folders): set `folder_id` to the target folder.\n • CHANGE STATUS / ASSIGN / EDIT FIELD: set `values` with the attribute_id(s) you want to change.\n • RE-PARENT: set `parent_id`.\nYou can combine these in one call. Only fields you pass are changed; omitted fields are left as-is. Call list_attributes if you don't know the attribute_id, and list_folders if you need a folder_id.", {
|
|
70
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
71
|
+
board_id: z.string().describe("The board id."),
|
|
72
|
+
item_id: z.string().describe("The item id to update."),
|
|
73
|
+
folder_id: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("New folder id — use this to move the item between folders/stages."),
|
|
77
|
+
values: z
|
|
78
|
+
.array(valueSchema)
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Attribute values to set. Existing values for attributes you DON'T list are preserved."),
|
|
81
|
+
parent_id: z
|
|
82
|
+
.string()
|
|
83
|
+
.nullable()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("New parent item id, or null to detach from a parent."),
|
|
86
|
+
}, ({ workspace_id, board_id, item_id, ...body }) => run(() => client.put(`/workspaces/${workspace_id}/boards/${board_id}/items/${item_id}`, body)));
|
|
87
|
+
server.tool("delete_item", "Delete a ticket. This is a soft-delete in Infinity — the item is removed from views but recoverable from the trash in the Infinity UI for a limited time. Use with caution; always confirm with the user first.", {
|
|
88
|
+
workspace_id: z.string().describe("The workspace id."),
|
|
89
|
+
board_id: z.string().describe("The board id."),
|
|
90
|
+
item_id: z.string().describe("The item id to delete."),
|
|
91
|
+
}, ({ workspace_id, board_id, item_id }) => run(() => client.delete(`/workspaces/${workspace_id}/boards/${board_id}/items/${item_id}`)));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.js","sourceRoot":"","sources":["../../src/tools/items.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ToolResult = {
|
|
2
|
+
content: Array<{
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
}>;
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function ok(data: unknown): ToolResult;
|
|
9
|
+
export declare function err(message: string): ToolResult;
|
|
10
|
+
export declare function run(fn: () => Promise<unknown>): Promise<ToolResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function ok(data) {
|
|
2
|
+
const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
3
|
+
return { content: [{ type: "text", text }] };
|
|
4
|
+
}
|
|
5
|
+
export function err(message) {
|
|
6
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
7
|
+
}
|
|
8
|
+
export async function run(fn) {
|
|
9
|
+
try {
|
|
10
|
+
return ok(await fn());
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/tools/util.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,EAAE,CAAC,IAAa;IAC9B,MAAM,IAAI,GACR,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,EAA0B;IAClD,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { run } from "./util.js";
|
|
2
|
+
export function registerWorkspaceTools(server, client) {
|
|
3
|
+
server.tool("list_workspaces", "List all Infinity workspaces the authenticated user can access. Returns workspace id and name for each. Call this first when the user has not specified a workspace — Claude needs a workspace_id to use any other tool.", {}, () => run(() => client.get("/workspaces")));
|
|
4
|
+
server.tool("get_profile", "Return the profile (id, name, email) of the user who owns the Infinity API token. Use this when you need to know who 'I' or 'me' refers to — for example, to filter items assigned to the current user.", {}, () => run(() => client.get("/profile")));
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=workspaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../../src/tools/workspaces.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,MAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,0NAA0N,EAC1N,EAAE,EACF,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAC3C,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,aAAa,EACb,yMAAyM,EACzM,EAAE,EACF,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CACxC,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface InfinityListResponse<T> {
|
|
2
|
+
data: T[];
|
|
3
|
+
object?: "list";
|
|
4
|
+
after?: string | null;
|
|
5
|
+
before?: string | null;
|
|
6
|
+
has_more?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface InfinityValue {
|
|
9
|
+
id: string;
|
|
10
|
+
object: "value";
|
|
11
|
+
data: unknown;
|
|
12
|
+
attribute_id: string;
|
|
13
|
+
item_id: string;
|
|
14
|
+
deleted: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface InfinityItem {
|
|
17
|
+
id: string;
|
|
18
|
+
object: "item";
|
|
19
|
+
folder_id: string;
|
|
20
|
+
parent_id: string | null;
|
|
21
|
+
created_at: string;
|
|
22
|
+
sort_order?: string;
|
|
23
|
+
values?: InfinityValue[];
|
|
24
|
+
deleted: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface InfinityAttribute {
|
|
27
|
+
id: string;
|
|
28
|
+
object: "attribute";
|
|
29
|
+
name: string;
|
|
30
|
+
type: string;
|
|
31
|
+
default_data?: unknown;
|
|
32
|
+
settings?: Record<string, unknown>;
|
|
33
|
+
created_by?: number | string;
|
|
34
|
+
created_at: string;
|
|
35
|
+
deleted: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface InfinityFolder {
|
|
38
|
+
id: string;
|
|
39
|
+
object: "folder";
|
|
40
|
+
name: string;
|
|
41
|
+
parent_id?: string | null;
|
|
42
|
+
created_at: string;
|
|
43
|
+
deleted?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface InfinityBoard {
|
|
46
|
+
id: string;
|
|
47
|
+
object: "board";
|
|
48
|
+
name: string;
|
|
49
|
+
workspace_id?: string;
|
|
50
|
+
created_at: string;
|
|
51
|
+
}
|
|
52
|
+
export interface InfinityWorkspace {
|
|
53
|
+
id: string;
|
|
54
|
+
object: "workspace";
|
|
55
|
+
name: string;
|
|
56
|
+
created_at: string;
|
|
57
|
+
}
|
|
58
|
+
export interface InfinityComment {
|
|
59
|
+
id: string;
|
|
60
|
+
object: "comment";
|
|
61
|
+
text: string;
|
|
62
|
+
item_id: string;
|
|
63
|
+
parent_id?: string | null;
|
|
64
|
+
created_at: string;
|
|
65
|
+
created_by?: number | string;
|
|
66
|
+
}
|
|
67
|
+
export interface InfinityProfile {
|
|
68
|
+
id: string | number;
|
|
69
|
+
object?: "user";
|
|
70
|
+
name?: string;
|
|
71
|
+
email?: string;
|
|
72
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slyswiss/infinity-mcp",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Community MCP server for Infinity (startinfinity.com). Not affiliated with Infinity Innovations Inc.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"infinity-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"claude",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"infinity",
|
|
28
|
+
"startinfinity",
|
|
29
|
+
"task-management",
|
|
30
|
+
"project-management"
|
|
31
|
+
],
|
|
32
|
+
"author": "Sly Swiss",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://gitlab.com/sly-swiss/infinity-mcp.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://gitlab.com/sly-swiss/infinity-mcp#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://gitlab.com/sly-swiss/infinity-mcp/-/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
|
+
"zod": "^3.23.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
54
|
+
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
55
|
+
"@semantic-release/git": "^10.0.1",
|
|
56
|
+
"@semantic-release/gitlab": "^13.2.0",
|
|
57
|
+
"@semantic-release/npm": "^12.0.1",
|
|
58
|
+
"@semantic-release/release-notes-generator": "^14.0.0",
|
|
59
|
+
"@types/node": "^20.14.0",
|
|
60
|
+
"semantic-release": "^24.2.0",
|
|
61
|
+
"typescript": "^5.4.0"
|
|
62
|
+
}
|
|
63
|
+
}
|