talentos 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TalentOS
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,148 @@
1
+ # talentos
2
+
3
+ A thin [stdio MCP](https://modelcontextprotocol.io) server that wraps the TalentOS
4
+ external HTTP API (`/api/v0/*`). Point any MCP-capable agent (Claude Code, Claude
5
+ Desktop, Cursor, etc.) at it and the agent can read your org's workflows and
6
+ nodes, then create or replace workflow drafts through the same API key.
7
+
8
+ It exposes six tools:
9
+
10
+ - **`get_context`** (no arguments) — one compact org summary: company profile
11
+ basics plus the names of the org's published workflows and nodes.
12
+ Good first call to prime an agent.
13
+ - **`list_workflows`** (no arguments) — published workflows plus drafts created
14
+ by the API key's owner as a compact text list (status, title, id, step count,
15
+ department, description, ISO `updatedAt`).
16
+ - **`get_workflow`** (`{ workflowId: string }`) — one workflow as an
17
+ agent-ready Markdown runbook, including its `**Version:**` stamp.
18
+ - **`list_nodes`** (no arguments) — company-wide reusable nodes as name, id,
19
+ step type, summary, and tools.
20
+ - **`create_workflow`** (`{ title, steps, connections }`) — create a new draft
21
+ workflow from a full plan. Steps may include `outputs`, `successCriteria`, and
22
+ `runtime` contract fields. Writes always create drafts; a human publishes in
23
+ the app.
24
+ - **`update_workflow`** (`{ workflowId, title, steps, connections }`) —
25
+ replace an existing draft workflow from a full plan. Steps may include
26
+ `outputs`, `successCriteria`, and `runtime`; published workflows return a
27
+ clear conflict instead of being edited.
28
+
29
+ The server holds no state and stores nothing. Every call is a single
30
+ authenticated request to the HTTP API; all authorization and visibility rules
31
+ live server-side.
32
+
33
+ ## Configuration
34
+
35
+ Two environment variables, both required. The server exits immediately with a
36
+ message on stderr if either is missing.
37
+
38
+ | Variable | Required | Description |
39
+ | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
40
+ | `TALENTOS_API_KEY` | yes | An API key you create in the TalentOS admin UI. Sent as `Authorization: Bearer <key>`. Never logged. |
41
+ | `TALENTOS_API_URL` | yes | The deployment's **`https://<name>.convex.site`** base URL — the `.convex.site` domain, **not** `.convex.cloud`. |
42
+
43
+ ## Install
44
+
45
+ Run the server with `npx` — no clone or build required. Your MCP client
46
+ launches it on demand; `npx` fetches and caches `talentos` on first use:
47
+
48
+ ```bash
49
+ npx -y talentos
50
+ ```
51
+
52
+ Set the two environment variables above in your MCP client config (snippets
53
+ below), not on the command line. The `-y` flag skips npx's install prompt so it
54
+ also works in noninteractive clients.
55
+
56
+ ### Run from source (contributors)
57
+
58
+ Working on the server itself? It keeps its own dependencies and lockfile — the
59
+ repo-root `pnpm install` does **not** install them. Once per clone:
60
+
61
+ ```bash
62
+ cd tools/mcp-server && pnpm install --ignore-workspace
63
+ ```
64
+
65
+ (`--ignore-workspace` keeps it out of the root lockfile — this package is
66
+ deliberately not part of the app workspace.) To point a client at your
67
+ checkout, set `command` to the package-local tsx binary
68
+ (`tools/mcp-server/node_modules/.bin/tsx`) and `args` to `src/index.ts`, both
69
+ as absolute paths. Two launch paths that look equivalent are not:
70
+
71
+ - `npx tsx …` resolves `tsx` on demand, so a client machine without it cached
72
+ does a network fetch at launch — in a noninteractive or offline MCP client
73
+ this fails (`ENOTCACHED`).
74
+ - `pnpm start` prints its script banner to **stdout**, which corrupts the MCP
75
+ JSON-RPC stream (`pnpm --silent` avoids it, but the plain form is a trap).
76
+
77
+ ## Add it to Claude Code
78
+
79
+ From anywhere — npx fetches the package, so no checkout is needed:
80
+
81
+ ```bash
82
+ claude mcp add talentos \
83
+ -e TALENTOS_API_KEY=tos_your_key_here \
84
+ -e TALENTOS_API_URL=https://your-deployment.convex.site \
85
+ -- npx -y talentos
86
+ ```
87
+
88
+ Then ask Claude Code to *"list our TalentOS workflows and show me one as
89
+ markdown"* — it calls `list_workflows`, picks an id, and calls `get_workflow`.
90
+
91
+ ## Add it to any other MCP client
92
+
93
+ Add this to your MCP client config (`.mcp.json`, Claude Desktop config, etc.).
94
+ `npx` resolves and caches the package, so there are no repo paths to wire up:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "talentos": {
100
+ "command": "npx",
101
+ "args": ["-y", "talentos"],
102
+ "env": {
103
+ "TALENTOS_API_KEY": "tos_your_key_here",
104
+ "TALENTOS_API_URL": "https://your-deployment.convex.site"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## Five-minute smoke test
112
+
113
+ 1. **Create a key.** In the TalentOS admin UI, create an API key. Copy the
114
+ `tos_...` value (you only see it once).
115
+
116
+ 2. **Curl the list endpoint** to confirm the key and URL work before involving an
117
+ agent:
118
+
119
+ ```bash
120
+ curl -H "Authorization: Bearer tos_your_key_here" \
121
+ https://your-deployment.convex.site/api/v0/workflows
122
+ ```
123
+
124
+ You should get `{ "workflows": [ ... ] }`. A `401` means the key is wrong;
125
+ make sure you used the `.convex.site` domain, not `.convex.cloud`.
126
+
127
+ 3. **Add the server** to your MCP client using a snippet above, with your real
128
+ key and URL.
129
+
130
+ 4. **Ask the agent** to list your workflows, then create a draft:
131
+
132
+ > List my TalentOS workflows, show me the first one as markdown, then create
133
+ > a new draft workflow for candidate outreach.
134
+
135
+ The agent calls `list_workflows`, `get_workflow`, and `create_workflow`. The
136
+ new workflow lands as a draft in TalentOS for a human to review and publish.
137
+
138
+ ## Errors you might see
139
+
140
+ - **Unauthorized (401)** — `TALENTOS_API_KEY` is missing, wrong, or revoked.
141
+ - **Workflow not found (404)** — the `id` doesn't exist or it is not visible to
142
+ this API key.
143
+ - **Published workflow conflict (409)** — `update_workflow` only edits drafts;
144
+ published workflows must be duplicated or edited in TalentOS.
145
+ - **Rate limited (429)** — the key is over its requests-per-minute budget;
146
+ wait a moment and retry.
147
+ - **Could not reach the API** — `TALENTOS_API_URL` is wrong or unreachable;
148
+ confirm it is the `.convex.site` URL.
package/dist/api.js ADDED
@@ -0,0 +1,213 @@
1
+ // The HTTP-client half of the MCP server: one function per TalentOS
2
+ // /api/v0/* endpoint, returning MCP-shaped text results. Deliberately free of
3
+ // MCP SDK imports (ToolResult is structurally CallToolResult-compatible) so
4
+ // these handlers unit-test from the repo root against a mocked fetch without
5
+ // installing this package's dependencies. Zero business logic lives here —
6
+ // authz, org scoping, and visibility are all server-side.
7
+ const WORKFLOWS_PATH = "/api/v0/workflows";
8
+ const NODES_PATH = "/api/v0/nodes";
9
+ const CONTEXT_PATH = "/api/v0/context";
10
+ // A hung deployment (connection accepted, no response) would otherwise leave the
11
+ // tool call pending forever with no error; abort so it surfaces as networkError.
12
+ const REQUEST_TIMEOUT_MS = 15_000;
13
+ export async function listWorkflows(config) {
14
+ const result = await fetchJson(config, WORKFLOWS_PATH);
15
+ if ("error" in result) {
16
+ return result.error;
17
+ }
18
+ // A wrong-URL proxy or shape drift can return a non-array here; treat that
19
+ // like the other malformed-response cases instead of throwing on .map.
20
+ const workflows = Array.isArray(result.body.workflows)
21
+ ? result.body.workflows
22
+ : [];
23
+ if (workflows.length === 0) {
24
+ return textResult("No workflows are visible to this API key.");
25
+ }
26
+ const lines = workflows.map((workflow) => {
27
+ const parts = [
28
+ `- ${workflow.title} (id: ${workflow.id})`,
29
+ `status: ${workflow.status}`,
30
+ `${workflow.stepCount} step(s)`,
31
+ `dept: ${workflow.department ?? "—"}`,
32
+ `updated ${workflow.updatedAt ?? "unknown"}`,
33
+ ];
34
+ if (workflow.description) {
35
+ parts.push(workflow.description);
36
+ }
37
+ return parts.join(" · ");
38
+ });
39
+ return textResult(`${workflows.length} workflow(s):\n${lines.join("\n")}`);
40
+ }
41
+ export async function getWorkflow(config, workflowId) {
42
+ const url = `${config.baseUrl}${WORKFLOWS_PATH}/${encodeURIComponent(workflowId)}`;
43
+ let response;
44
+ try {
45
+ response = await fetch(url, {
46
+ headers: {
47
+ Accept: "text/markdown",
48
+ Authorization: `Bearer ${config.apiKey}`,
49
+ },
50
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
51
+ });
52
+ }
53
+ catch (error) {
54
+ return networkError(error);
55
+ }
56
+ if (!response.ok) {
57
+ return await httpError(response, workflowId);
58
+ }
59
+ // The wrong-URL failure mode is a login page or JSON error served from the
60
+ // wrong domain; a rewritten/stripped content-type on a real runbook is not.
61
+ // Reject only the clearly-wrong types so a text/plain runbook still passes.
62
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
63
+ if (contentType.includes("text/html") ||
64
+ contentType.includes("application/json")) {
65
+ return toolError(`Expected a Markdown runbook but got content-type "${contentType || "unknown"}". ${URL_HINT}`);
66
+ }
67
+ try {
68
+ return textResult(await response.text());
69
+ }
70
+ catch (error) {
71
+ return networkError(error);
72
+ }
73
+ }
74
+ export async function listNodes(config) {
75
+ const result = await fetchJson(config, NODES_PATH);
76
+ if ("error" in result) {
77
+ return result.error;
78
+ }
79
+ const nodes = Array.isArray(result.body.nodes) ? result.body.nodes : [];
80
+ if (nodes.length === 0) {
81
+ return textResult("No company nodes are published.");
82
+ }
83
+ const lines = nodes.map((node) => {
84
+ const parts = [
85
+ `- ${node.name} (id: ${node.id})`,
86
+ node.stepType,
87
+ node.summary,
88
+ ];
89
+ if (Array.isArray(node.toolNames) && node.toolNames.length > 0) {
90
+ parts.push(`tools: ${node.toolNames.join(", ")}`);
91
+ }
92
+ return parts.join(" · ");
93
+ });
94
+ return textResult(`${nodes.length} node(s):\n${lines.join("\n")}`);
95
+ }
96
+ export async function getContext(config) {
97
+ const result = await fetchJson(config, CONTEXT_PATH);
98
+ if ("error" in result) {
99
+ return result.error;
100
+ }
101
+ // The org summary as-is: it is already a compact priming payload, and any
102
+ // formatting opinion belongs server-side, not in this wrapper.
103
+ return textResult(JSON.stringify(result.body, null, 2));
104
+ }
105
+ export async function createWorkflow(config, plan) {
106
+ return await writeWorkflow(config, WORKFLOWS_PATH, plan, "Created");
107
+ }
108
+ export async function updateWorkflow(config, workflowId, plan) {
109
+ return await writeWorkflow(config, `${WORKFLOWS_PATH}/${encodeURIComponent(workflowId)}`, plan, "Updated", workflowId);
110
+ }
111
+ const URL_HINT = "The TALENTOS_API_URL may be wrong — it must point at the deployment's https://<name>.convex.site domain.";
112
+ async function writeWorkflow(config, path, plan, action, workflowId) {
113
+ const result = await fetchJson(config, path, path === WORKFLOWS_PATH ? "POST" : "PUT", plan, workflowId);
114
+ if ("error" in result) {
115
+ return result.error;
116
+ }
117
+ const id = result.body.id ?? workflowId;
118
+ const status = result.body.status ?? "draft";
119
+ if (!id) {
120
+ return toolError(`The response was missing a workflow id. ${URL_HINT}`);
121
+ }
122
+ return textResult(`${action} workflow draft (id: ${id}) · status: ${status}\nA human must publish it in TalentOS.`);
123
+ }
124
+ function textResult(text) {
125
+ return { content: [{ text, type: "text" }] };
126
+ }
127
+ function toolError(text) {
128
+ return { content: [{ text, type: "text" }], isError: true };
129
+ }
130
+ async function httpError(response, workflowId) {
131
+ const { message } = await parseErrorBody(response);
132
+ if (response.status === 400 && message) {
133
+ return toolError(message);
134
+ }
135
+ if (response.status === 401) {
136
+ return toolError("Unauthorized (401). Check that TALENTOS_API_KEY is a valid, active key for this deployment.");
137
+ }
138
+ if (response.status === 404 && workflowId) {
139
+ return toolError(`No workflow with id "${workflowId}" exists, or it is not visible to this API key.`);
140
+ }
141
+ if (response.status === 409) {
142
+ return toolError(message ??
143
+ "This workflow is published and can't be edited through the API.");
144
+ }
145
+ if (response.status === 429) {
146
+ return toolError("Rate limited (429). This API key is over its requests-per-minute budget — wait a moment and retry.");
147
+ }
148
+ if (message) {
149
+ return toolError(`The TalentOS API returned ${response.status}: ${message}`);
150
+ }
151
+ return toolError(`The TalentOS API returned an unexpected status ${response.status}.`);
152
+ }
153
+ async function parseErrorBody(response) {
154
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
155
+ if (!contentType.includes("application/json")) {
156
+ return {};
157
+ }
158
+ try {
159
+ const body = await response.json();
160
+ if (typeof body === "object" &&
161
+ body !== null &&
162
+ typeof body.error === "string") {
163
+ return { message: body.error };
164
+ }
165
+ }
166
+ catch {
167
+ return {};
168
+ }
169
+ return {};
170
+ }
171
+ async function fetchJson(config, path, method = "GET", body, workflowId) {
172
+ let response;
173
+ try {
174
+ response = await fetch(`${config.baseUrl}${path}`, {
175
+ body: body === undefined ? undefined : JSON.stringify(body),
176
+ headers: {
177
+ Accept: "application/json",
178
+ Authorization: `Bearer ${config.apiKey}`,
179
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
180
+ },
181
+ method,
182
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
183
+ });
184
+ }
185
+ catch (error) {
186
+ return { error: networkError(error) };
187
+ }
188
+ if (!response.ok) {
189
+ return { error: await httpError(response, workflowId) };
190
+ }
191
+ let parsed;
192
+ try {
193
+ parsed = await response.json();
194
+ }
195
+ catch {
196
+ return {
197
+ error: toolError(`The response was not valid JSON. ${URL_HINT}`),
198
+ };
199
+ }
200
+ if (parsed === null || typeof parsed !== "object") {
201
+ return {
202
+ error: toolError(`The response was not a JSON object. ${URL_HINT}`),
203
+ };
204
+ }
205
+ return { body: parsed };
206
+ }
207
+ function networkError(error) {
208
+ if (error instanceof Error && error.name === "TimeoutError") {
209
+ return toolError(`The TalentOS API did not respond within ${REQUEST_TIMEOUT_MS / 1000}s. The deployment may be overloaded or unreachable — check TALENTOS_API_URL and retry.`);
210
+ }
211
+ const detail = error instanceof Error ? error.message : String(error);
212
+ return toolError(`Could not reach the TalentOS API. Check TALENTOS_API_URL points at the deployment's .convex.site domain. (${detail})`);
213
+ }
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ // Stdio MCP server wrapping the TalentOS external HTTP API — the MCP wiring
3
+ // only. The tool handlers live in api.ts (pure fetch wrappers) so they
4
+ // unit-test without this file's SDK dependencies.
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { z } from "zod";
8
+ import { createWorkflow, getContext, getWorkflow, listNodes, listWorkflows, updateWorkflow, } from "./api.js";
9
+ const TRAILING_SLASHES = /\/+$/;
10
+ const WORKFLOW_STEP_SCHEMA = z.object({
11
+ key: z.string().describe("Short kebab-case slug, unique per step."),
12
+ title: z.string().describe("Short imperative phrase, at most 8 words."),
13
+ stepType: z.enum(["human", "ai_assisted", "ai_agent", "automation"]),
14
+ description: z
15
+ .string()
16
+ .optional()
17
+ .describe("Complete markdown instructions for the step."),
18
+ dataNeeded: z
19
+ .array(z.object({
20
+ label: z.string().describe("Short input name."),
21
+ detail: z
22
+ .string()
23
+ .optional()
24
+ .describe("Optional detail about the input."),
25
+ }))
26
+ .optional()
27
+ .describe("Inputs the step needs."),
28
+ outputs: z
29
+ .array(z.object({
30
+ label: z.string().describe("Artifact or deliverable name."),
31
+ detail: z
32
+ .string()
33
+ .optional()
34
+ .describe("Optional detail about the output."),
35
+ format: z
36
+ .string()
37
+ .optional()
38
+ .describe("Optional format hint, such as markdown, json, or url."),
39
+ }))
40
+ .optional()
41
+ .describe("Artifacts or deliverables the step produces."),
42
+ successCriteria: z
43
+ .string()
44
+ .optional()
45
+ .describe("How a reviewer or agent knows this step is done well."),
46
+ runtime: z
47
+ .object({
48
+ kind: z
49
+ .string()
50
+ .describe('Runtime the user named, such as "claude-code" or "api".'),
51
+ model: z.string().optional().describe("Optional model name."),
52
+ config: z
53
+ .record(z.string(), z.string())
54
+ .optional()
55
+ .describe("Optional string config for the runtime."),
56
+ })
57
+ .optional()
58
+ .describe("What executes this step, when the user names a runtime."),
59
+ toolNames: z
60
+ .array(z.string())
61
+ .optional()
62
+ .describe("Tool names exactly as the user named them."),
63
+ });
64
+ const WORKFLOW_CONNECTION_SCHEMA = z.object({
65
+ fromKey: z.string().describe("The source step key."),
66
+ toKey: z.string().describe("The target step key."),
67
+ label: z
68
+ .string()
69
+ .optional()
70
+ .describe("Short branch label, only when the flow branches."),
71
+ });
72
+ async function main() {
73
+ const config = loadConfig();
74
+ const server = new McpServer({
75
+ name: "talentos",
76
+ version: "0.1.0",
77
+ });
78
+ server.registerTool("list_workflows", {
79
+ title: "List workflows",
80
+ description: "List the TalentOS workflows visible to this API key: published workflows plus drafts created by the key's owner. Returns status, title, id, step count, department, and description for each so you can pick one to fetch or update.",
81
+ }, () => listWorkflows(config));
82
+ server.registerTool("get_workflow", {
83
+ title: "Get workflow",
84
+ description: "Fetch a single visible TalentOS workflow as an agent-ready Markdown runbook. Pass the workflow id from list_workflows.",
85
+ inputSchema: {
86
+ workflowId: z
87
+ .string()
88
+ .describe("The workflow id, as returned by list_workflows."),
89
+ },
90
+ }, ({ workflowId }) => getWorkflow(config, workflowId));
91
+ server.registerTool("list_nodes", {
92
+ title: "List nodes",
93
+ description: "List the org's company-wide TalentOS nodes (reusable, admin-promoted workflow steps). Returns name, id, step type, and summary for each.",
94
+ }, () => listNodes(config));
95
+ server.registerTool("get_context", {
96
+ title: "Get org context",
97
+ description: "One compact summary of the org this API key belongs to: company profile basics plus the names of its published workflows and nodes. Call this first to prime yourself.",
98
+ }, () => getContext(config));
99
+ server.registerTool("create_workflow", {
100
+ title: "Create workflow draft",
101
+ description: "Create a new TalentOS workflow draft from a full title/steps/connections plan. Steps may include agent contract fields: outputs, successCriteria, and runtime. Writes always create drafts that a human publishes in the app.",
102
+ inputSchema: {
103
+ title: z.string().describe("Short name for the whole workflow."),
104
+ steps: z
105
+ .array(WORKFLOW_STEP_SCHEMA)
106
+ .describe("The ordered workflow steps to materialize."),
107
+ connections: z
108
+ .array(WORKFLOW_CONNECTION_SCHEMA)
109
+ .optional()
110
+ .describe("Directed edges between the steps. Omit for no edges."),
111
+ },
112
+ }, ({ connections, steps, title }) => createWorkflow(config, {
113
+ connections: connections ?? [],
114
+ steps,
115
+ title,
116
+ }));
117
+ server.registerTool("update_workflow", {
118
+ title: "Replace workflow draft",
119
+ description: "Replace an existing TalentOS workflow draft with a full title/steps/connections plan. Steps may include agent contract fields: outputs, successCriteria, and runtime. Drafts only: updating a published workflow returns a clear conflict message.",
120
+ inputSchema: {
121
+ workflowId: z.string().describe("The workflow id from list_workflows."),
122
+ title: z.string().describe("Short name for the whole workflow."),
123
+ steps: z
124
+ .array(WORKFLOW_STEP_SCHEMA)
125
+ .describe("The complete replacement step list."),
126
+ connections: z
127
+ .array(WORKFLOW_CONNECTION_SCHEMA)
128
+ .optional()
129
+ .describe("The complete replacement edge list. Omit for no edges."),
130
+ },
131
+ }, ({ connections, steps, title, workflowId }) => updateWorkflow(config, workflowId, {
132
+ connections: connections ?? [],
133
+ steps,
134
+ title,
135
+ }));
136
+ await server.connect(new StdioServerTransport());
137
+ }
138
+ function loadConfig() {
139
+ const apiKey = process.env.TALENTOS_API_KEY?.trim();
140
+ const rawUrl = process.env.TALENTOS_API_URL?.trim();
141
+ if (!apiKey) {
142
+ exitWithConfigError("TALENTOS_API_KEY is not set. Create an API key in the TalentOS admin UI and pass it via the env block of your MCP client config.");
143
+ }
144
+ if (!rawUrl) {
145
+ exitWithConfigError("TALENTOS_API_URL is not set. It must be the deployment's https://<name>.convex.site URL (not .convex.cloud).");
146
+ }
147
+ return { apiKey, baseUrl: rawUrl.replace(TRAILING_SLASHES, "") };
148
+ }
149
+ function exitWithConfigError(message) {
150
+ process.stderr.write(`[talentos] ${message}\n`);
151
+ process.exit(1);
152
+ }
153
+ main().catch((error) => {
154
+ const detail = error instanceof Error ? error.message : String(error);
155
+ process.stderr.write(`[talentos] fatal: ${detail}\n`);
156
+ process.exit(1);
157
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "talentos",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "Stdio MCP server wrapping the TalentOS external HTTP API for workflow reads and draft writes.",
7
+ "bin": {
8
+ "talentos": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "start": "tsx src/index.ts",
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "typecheck": "tsc --noEmit",
20
+ "prepack": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.29.0",
24
+ "zod": "^4.4.3"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^26.1.0",
28
+ "tsx": "^4.22.5",
29
+ "typescript": "^6.0.3"
30
+ }
31
+ }