@tacitai/mcp-server 0.1.1 → 0.2.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/dist/auth.d.ts +32 -0
- package/dist/auth.js +64 -0
- package/dist/client.d.ts +60 -5
- package/dist/client.js +234 -53
- package/dist/index.js +128 -106
- package/dist/oauth.d.ts +115 -0
- package/dist/oauth.js +619 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +12 -0
- package/dist/setup.d.ts +28 -5
- package/dist/setup.js +266 -59
- package/dist/tools/agents.d.ts +277 -0
- package/dist/tools/agents.js +335 -0
- package/dist/tools/annotations.d.ts +117 -0
- package/dist/tools/annotations.js +130 -0
- package/dist/tools/artifacts.d.ts +178 -8
- package/dist/tools/artifacts.js +372 -81
- package/dist/tools/chronicle.d.ts +3 -0
- package/dist/tools/chronicle.js +165 -0
- package/dist/tools/connector_write_back.d.ts +3 -0
- package/dist/tools/connector_write_back.js +27 -0
- package/dist/tools/converse.d.ts +10 -0
- package/dist/tools/converse.js +88 -0
- package/dist/tools/crews.d.ts +3 -0
- package/dist/tools/crews.js +115 -0
- package/dist/tools/drafts.d.ts +83 -0
- package/dist/tools/drafts.js +86 -0
- package/dist/tools/errors.d.ts +15 -0
- package/dist/tools/errors.js +21 -0
- package/dist/tools/export.d.ts +54 -0
- package/dist/tools/export.js +55 -0
- package/dist/tools/gm.d.ts +214 -0
- package/dist/tools/gm.js +198 -0
- package/dist/tools/ingest.d.ts +58 -4
- package/dist/tools/ingest.js +109 -76
- package/dist/tools/kb.d.ts +544 -0
- package/dist/tools/kb.js +687 -0
- package/dist/tools/lake-structure.d.ts +3 -0
- package/dist/tools/lake-structure.js +126 -0
- package/dist/tools/lake.d.ts +932 -0
- package/dist/tools/lake.js +1017 -0
- package/dist/tools/move.d.ts +200 -0
- package/dist/tools/move.js +96 -0
- package/dist/tools/outbox.d.ts +274 -0
- package/dist/tools/outbox.js +223 -0
- package/dist/tools/people.d.ts +3 -0
- package/dist/tools/people.js +59 -0
- package/dist/tools/pyramid.d.ts +554 -0
- package/dist/tools/pyramid.js +758 -0
- package/dist/tools/recall.d.ts +2 -0
- package/dist/tools/recall.js +28 -17
- package/dist/tools/registry.d.ts +17 -0
- package/dist/tools/registry.js +197 -0
- package/dist/tools/research.d.ts +47 -0
- package/dist/tools/research.js +575 -0
- package/dist/tools/search.d.ts +9 -0
- package/dist/tools/search.js +86 -25
- package/dist/tools/sharing.d.ts +63 -0
- package/dist/tools/sharing.js +157 -0
- package/dist/tools/synthesize.d.ts +54 -2
- package/dist/tools/synthesize.js +80 -23
- package/dist/tools/web-url.d.ts +57 -0
- package/dist/tools/web-url.js +74 -0
- package/dist/tools/workspaces.d.ts +3 -0
- package/dist/tools/workspaces.js +47 -0
- package/dist/transport.d.ts +9 -0
- package/dist/transport.js +209 -0
- package/package.json +5 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
export interface ToolRequest {
|
|
4
|
+
execute(): Promise<{
|
|
5
|
+
content: Array<{
|
|
6
|
+
type: 'text';
|
|
7
|
+
text: string;
|
|
8
|
+
}>;
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare const webUrlInputSchema: z.ZodObject<{
|
|
13
|
+
entity_type: z.ZodEnum<["artifact", "kb_article", "dossier", "dossier_summary", "dossier_scorecard", "dossier_deliverable", "guest", "knowledge"]>;
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
collection_id: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
id: string;
|
|
18
|
+
entity_type: "artifact" | "kb_article" | "dossier" | "dossier_summary" | "dossier_scorecard" | "dossier_deliverable" | "guest" | "knowledge";
|
|
19
|
+
collection_id?: string | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
id: string;
|
|
22
|
+
entity_type: "artifact" | "kb_article" | "dossier" | "dossier_summary" | "dossier_scorecard" | "dossier_deliverable" | "guest" | "knowledge";
|
|
23
|
+
collection_id?: string | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type WebUrlInput = z.infer<typeof webUrlInputSchema>;
|
|
26
|
+
export declare const WEB_URL_TOOL_DEF: {
|
|
27
|
+
name: "tacit_web_url";
|
|
28
|
+
description: string;
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object";
|
|
31
|
+
properties: {
|
|
32
|
+
entity_type: {
|
|
33
|
+
type: string;
|
|
34
|
+
enum: ("artifact" | "kb_article" | "dossier" | "dossier_summary" | "dossier_scorecard" | "dossier_deliverable" | "guest" | "knowledge")[];
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
id: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
collection_id: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
required: string[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function buildWebUrlRequest(input: WebUrlInput): ToolRequest;
|
|
50
|
+
export declare function handleWebUrl(args: unknown): Promise<{
|
|
51
|
+
content: Array<{
|
|
52
|
+
type: 'text';
|
|
53
|
+
text: string;
|
|
54
|
+
}>;
|
|
55
|
+
isError?: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
export declare function registerWebUrlTools(server: McpServer): void;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getClient } from '../client.js';
|
|
4
|
+
const ENTITY_TYPES = [
|
|
5
|
+
'artifact',
|
|
6
|
+
'kb_article',
|
|
7
|
+
'dossier',
|
|
8
|
+
'dossier_summary',
|
|
9
|
+
'dossier_scorecard',
|
|
10
|
+
'dossier_deliverable',
|
|
11
|
+
'guest',
|
|
12
|
+
'knowledge',
|
|
13
|
+
];
|
|
14
|
+
export const webUrlInputSchema = z.object({
|
|
15
|
+
entity_type: z.enum(ENTITY_TYPES).describe('Entity type: artifact, kb_article, dossier, dossier_summary, dossier_scorecard, dossier_deliverable, guest, knowledge'),
|
|
16
|
+
id: z.string().min(1).describe('UUID of the entity'),
|
|
17
|
+
collection_id: z.string().optional().describe('Required for kb_article (the parent collection UUID) and dossier_deliverable (the parent dossier UUID)'),
|
|
18
|
+
});
|
|
19
|
+
export const WEB_URL_TOOL_DEF = {
|
|
20
|
+
name: 'tacit_web_url',
|
|
21
|
+
description: 'Get the TACIT web app URL for an entity. Returns a direct browser link. ' +
|
|
22
|
+
'Requires TACIT_APP_URL to be configured. For kb_article, collection_id is required.',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
entity_type: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
enum: [...ENTITY_TYPES],
|
|
29
|
+
description: 'Entity type',
|
|
30
|
+
},
|
|
31
|
+
id: { type: 'string', description: 'UUID of the entity' },
|
|
32
|
+
collection_id: { type: 'string', description: 'Parent collection UUID (required for kb_article, dossier_deliverable)' },
|
|
33
|
+
},
|
|
34
|
+
required: ['entity_type', 'id'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export function buildWebUrlRequest(input) {
|
|
38
|
+
return {
|
|
39
|
+
async execute() {
|
|
40
|
+
const client = getClient();
|
|
41
|
+
const url = client.webUrl(input.entity_type, input.id, {
|
|
42
|
+
collectionId: input.collection_id,
|
|
43
|
+
});
|
|
44
|
+
if (!url) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: 'TACIT_APP_URL is not configured. Set it in the MCP server environment or profile to enable web URLs.',
|
|
49
|
+
}],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: JSON.stringify({ web_url: url, entity_type: input.entity_type, id: input.id }) }],
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function handleWebUrl(args) {
|
|
60
|
+
const parsed = webUrlInputSchema.safeParse(args);
|
|
61
|
+
if (!parsed.success) {
|
|
62
|
+
return { content: [{ type: 'text', text: `Invalid input: ${parsed.error.message}` }], isError: true };
|
|
63
|
+
}
|
|
64
|
+
return buildWebUrlRequest(parsed.data).execute();
|
|
65
|
+
}
|
|
66
|
+
export function registerWebUrlTools(server) {
|
|
67
|
+
server.tool('tacit_web_url', 'Get the TACIT web app URL for an entity.', {
|
|
68
|
+
entity_type: z.enum(ENTITY_TYPES).describe('Entity type'),
|
|
69
|
+
id: z.string().min(1).describe('UUID of the entity'),
|
|
70
|
+
collection_id: z.string().optional().describe('Parent collection UUID (required for kb_article, dossier_deliverable)'),
|
|
71
|
+
}, async (args) => {
|
|
72
|
+
return buildWebUrlRequest(args).execute();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TacitApiError } from '../client.js';
|
|
3
|
+
import { success, error } from './errors.js';
|
|
4
|
+
export function registerWorkspaceTools(server, getClient) {
|
|
5
|
+
server.tool('tacit_list_workspaces', 'List all workspaces accessible to the authenticated user.', {}, async () => {
|
|
6
|
+
try {
|
|
7
|
+
const client = await getClient();
|
|
8
|
+
const data = await client.request('GET', '/api/v1/workspaces/mine');
|
|
9
|
+
return success(data);
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
if (e instanceof TacitApiError)
|
|
13
|
+
return error(e.message);
|
|
14
|
+
return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
server.tool('tacit_switch_workspace', 'Switch the active workspace. All subsequent tool calls will operate on the new workspace.', {
|
|
18
|
+
workspace_id: z.string().uuid().describe('UUID of the workspace to switch to'),
|
|
19
|
+
}, async (args) => {
|
|
20
|
+
try {
|
|
21
|
+
const client = await getClient();
|
|
22
|
+
const result = await client.switchWorkspace(args.workspace_id);
|
|
23
|
+
return success({
|
|
24
|
+
message: `Switched to workspace: ${result.name}`,
|
|
25
|
+
workspace_id: result.workspace_id,
|
|
26
|
+
name: result.name,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (e instanceof TacitApiError)
|
|
31
|
+
return error(e.message);
|
|
32
|
+
return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
server.tool('tacit_current_workspace', 'Get the currently active workspace ID and name.', {}, async () => {
|
|
36
|
+
try {
|
|
37
|
+
const client = await getClient();
|
|
38
|
+
return success({
|
|
39
|
+
workspace_id: client.workspaceId,
|
|
40
|
+
name: client.workspaceName,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return error(`Unexpected error: ${e instanceof Error ? e.message : String(e)}`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTPS (Streamable HTTP) transport for external MCP clients.
|
|
3
|
+
*
|
|
4
|
+
* Stateless: a fresh transport per request, caller's bearer token bound via
|
|
5
|
+
* AsyncLocalStorage. Requests are serialized because a single McpServer can
|
|
6
|
+
* only be attached to one transport at a time.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
export declare function startHttpTransport(server: McpServer): Promise<void>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* HTTPS (Streamable HTTP) transport for external MCP clients.
|
|
4
|
+
*
|
|
5
|
+
* Stateless: a fresh transport per request, caller's bearer token bound via
|
|
6
|
+
* AsyncLocalStorage. Requests are serialized because a single McpServer can
|
|
7
|
+
* only be attached to one transport at a time.
|
|
8
|
+
*/
|
|
9
|
+
import * as http from 'node:http';
|
|
10
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
11
|
+
import { RateLimiter, resolveAuth } from './auth.js';
|
|
12
|
+
import { getBlockedReason, isBlockedOnHttps } from './tools/registry.js';
|
|
13
|
+
import { runWithRequestContext } from './client.js';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { isOAuthEnabled, handleAuthorizeGet, handleAuthorizePost, handleTokenExchange, handleClientRegistration, handleDiscovery, handleProtectedResource, startCleanupInterval, } from './oauth.js';
|
|
16
|
+
const MAX_BODY_BYTES = 1_000_000;
|
|
17
|
+
function setCors(res, allowGet = false) {
|
|
18
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
19
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Methods', allowGet ? 'GET, POST, OPTIONS' : 'POST, OPTIONS');
|
|
21
|
+
}
|
|
22
|
+
function sendJson(res, status, body) {
|
|
23
|
+
if (res.headersSent)
|
|
24
|
+
return;
|
|
25
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
26
|
+
res.end(JSON.stringify(body));
|
|
27
|
+
}
|
|
28
|
+
function send401(res, message, invalidToken = false) {
|
|
29
|
+
if (res.headersSent)
|
|
30
|
+
return;
|
|
31
|
+
const wwwAuth = invalidToken
|
|
32
|
+
? 'Bearer error="invalid_token", error_description="The access token is expired or invalid"'
|
|
33
|
+
: 'Bearer';
|
|
34
|
+
res.writeHead(401, {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
'WWW-Authenticate': wwwAuth,
|
|
37
|
+
});
|
|
38
|
+
res.end(JSON.stringify({ error: message }));
|
|
39
|
+
}
|
|
40
|
+
function rpcError(id, code, message) {
|
|
41
|
+
return { jsonrpc: '2.0', id: id ?? null, error: { code, message } };
|
|
42
|
+
}
|
|
43
|
+
function readBody(req) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
let size = 0;
|
|
47
|
+
req.on('data', (chunk) => {
|
|
48
|
+
size += chunk.length;
|
|
49
|
+
if (size > MAX_BODY_BYTES) {
|
|
50
|
+
reject(new Error('body too large'));
|
|
51
|
+
req.destroy();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
});
|
|
56
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
57
|
+
req.on('error', reject);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function blockedToolCall(parsed) {
|
|
61
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
62
|
+
for (const msg of messages) {
|
|
63
|
+
if (msg && typeof msg === 'object' && msg['method'] === 'tools/call') {
|
|
64
|
+
const m = msg;
|
|
65
|
+
const name = typeof m.params?.name === 'string' ? m.params.name : '';
|
|
66
|
+
if (isBlockedOnHttps(name))
|
|
67
|
+
return { id: m.id, tool: name, reason: getBlockedReason(name) ?? 'unknown_tool' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function getClientIp(req) {
|
|
73
|
+
return req.headers['cf-connecting-ip']
|
|
74
|
+
?? req.headers['x-forwarded-for']?.split(',')[0]?.trim()
|
|
75
|
+
?? req.socket.remoteAddress
|
|
76
|
+
?? 'unknown';
|
|
77
|
+
}
|
|
78
|
+
export async function startHttpTransport(server) {
|
|
79
|
+
const port = Number.parseInt(process.env['MCP_HTTPS_PORT'] ?? '8319', 10);
|
|
80
|
+
const apiUrl = (process.env['TACIT_API_URL'] ?? 'http://127.0.0.1:7473').replace(/\/$/, '');
|
|
81
|
+
const externalUrl = (process.env['MCP_EXTERNAL_URL'] ?? `http://127.0.0.1:${port}`).replace(/\/$/, '');
|
|
82
|
+
const limiter = new RateLimiter();
|
|
83
|
+
if (isOAuthEnabled()) {
|
|
84
|
+
startCleanupInterval();
|
|
85
|
+
}
|
|
86
|
+
let queue = Promise.resolve();
|
|
87
|
+
const handle = async (req, res) => {
|
|
88
|
+
const pathname = (req.url ?? '/').split('?')[0];
|
|
89
|
+
process.stderr.write(`[tacit-mcp-http] ${req.method} ${pathname}\n`);
|
|
90
|
+
// ── OPTIONS (all paths) ─────────────────────────────────────────────
|
|
91
|
+
if (req.method === 'OPTIONS') {
|
|
92
|
+
const isOAuthPath = pathname === '/authorize' || pathname.startsWith('/.well-known/');
|
|
93
|
+
setCors(res, isOAuthPath);
|
|
94
|
+
res.writeHead(204);
|
|
95
|
+
res.end();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// ── Health ───────────────────────────────────────────────────────────
|
|
99
|
+
if (req.method === 'GET' && pathname === '/health') {
|
|
100
|
+
sendJson(res, 200, { status: 'ok' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// ── Rate limit (all endpoints) ──────────────────────────────────────
|
|
104
|
+
const ip = getClientIp(req);
|
|
105
|
+
if (!limiter.tryConsume(ip)) {
|
|
106
|
+
sendJson(res, 429, { error: 'rate limit exceeded' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// ── OAuth endpoints (bypass serialization queue) ────────────────────
|
|
110
|
+
if (isOAuthEnabled()) {
|
|
111
|
+
if (pathname === '/.well-known/oauth-authorization-server' && req.method === 'GET') {
|
|
112
|
+
setCors(res, true);
|
|
113
|
+
handleDiscovery(res, externalUrl);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (pathname === '/.well-known/oauth-protected-resource' && req.method === 'GET') {
|
|
117
|
+
setCors(res, true);
|
|
118
|
+
handleProtectedResource(res, `${externalUrl}/mcp`, externalUrl);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (pathname === '/authorize' && req.method === 'GET') {
|
|
122
|
+
setCors(res, true);
|
|
123
|
+
await handleAuthorizeGet(req, res);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (pathname === '/authorize' && req.method === 'POST') {
|
|
127
|
+
await handleAuthorizePost(req, res, apiUrl);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (pathname === '/token' && req.method === 'POST') {
|
|
131
|
+
await handleTokenExchange(req, res);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (pathname === '/register' && req.method === 'POST') {
|
|
135
|
+
setCors(res);
|
|
136
|
+
await handleClientRegistration(req, res);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ── MCP endpoint ────────────────────────────────────────────────────
|
|
141
|
+
if (pathname !== '/mcp' || req.method !== 'POST') {
|
|
142
|
+
setCors(res);
|
|
143
|
+
sendJson(res, 404, { error: 'not found' });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
setCors(res);
|
|
147
|
+
const auth = resolveAuth(req);
|
|
148
|
+
if (!auth) {
|
|
149
|
+
send401(res, 'missing or empty Bearer token');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (auth.type === 'invalid_token') {
|
|
153
|
+
send401(res, 'invalid or expired OAuth token', true);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = JSON.parse(await readBody(req));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
sendJson(res, 400, rpcError(null, -32700, 'Parse error'));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const blocked = blockedToolCall(parsed);
|
|
165
|
+
if (blocked) {
|
|
166
|
+
sendJson(res, 200, rpcError(blocked.id, -32601, `Tool not available over HTTPS: ${blocked.tool} (reason: ${blocked.reason})`));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const requestToken = auth.type === 'oauth' ? auth.info.tacit_token : auth.token;
|
|
170
|
+
const principal = auth.type === 'oauth'
|
|
171
|
+
? auth.info.user_email
|
|
172
|
+
: createHash('sha256').update(auth.token).digest('hex').slice(0, 16);
|
|
173
|
+
const run = async () => {
|
|
174
|
+
const transport = new StreamableHTTPServerTransport({
|
|
175
|
+
sessionIdGenerator: undefined,
|
|
176
|
+
enableJsonResponse: true,
|
|
177
|
+
});
|
|
178
|
+
const done = new Promise((resolve) => res.on('close', () => resolve()));
|
|
179
|
+
try {
|
|
180
|
+
await server.connect(transport);
|
|
181
|
+
await runWithRequestContext(requestToken, apiUrl, () => transport.handleRequest(req, res, parsed), principal);
|
|
182
|
+
await done;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
process.stderr.write(`[tacit-mcp-http] request error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
186
|
+
sendJson(res, 500, rpcError(null, -32603, 'Internal error'));
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await transport.close().catch(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const next = queue.then(run, run);
|
|
193
|
+
queue = next.catch(() => undefined);
|
|
194
|
+
await next;
|
|
195
|
+
};
|
|
196
|
+
const httpServer = http.createServer((req, res) => {
|
|
197
|
+
handle(req, res).catch((err) => {
|
|
198
|
+
process.stderr.write(`[tacit-mcp-http] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
199
|
+
sendJson(res, 500, { error: 'internal error' });
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
await new Promise((resolve, reject) => {
|
|
203
|
+
httpServer.once('error', reject);
|
|
204
|
+
httpServer.listen(port, '127.0.0.1', () => {
|
|
205
|
+
process.stderr.write(`[tacit-mcp-http] listening on 127.0.0.1:${port}\n`);
|
|
206
|
+
resolve();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tacitai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "stdio MCP server wrapping Tacit /api/v1 REST/SSE API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/scottkmcmillan/tacit.git",
|
|
8
|
+
"url": "git+https://github.com/scottkmcmillan/tacit.git",
|
|
9
9
|
"directory": "packages/tacit-mcp-server"
|
|
10
10
|
},
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
+
"type": "module",
|
|
14
15
|
"main": "dist/index.js",
|
|
15
16
|
"bin": {
|
|
16
|
-
"tacit-mcp-server": "
|
|
17
|
+
"tacit-mcp-server": "dist/index.js"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"test": "vitest run"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
35
36
|
"semver": "^7.6.0",
|
|
36
37
|
"zod": "^3.22.0"
|
|
37
38
|
},
|