tuttiai-mcp 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.
@@ -0,0 +1,29 @@
1
+ import { Voice, Permission, Tool, VoiceContext } from '@tuttiai/types';
2
+
3
+ interface McpVoiceOptions {
4
+ /** MCP server command, e.g. 'npx @playwright/mcp' */
5
+ server: string;
6
+ /** Additional CLI arguments appended after the server command. */
7
+ args?: string[];
8
+ /** Extra environment variables passed to the server process. */
9
+ env?: Record<string, string>;
10
+ /** Override the voice name (default: mcp-<server-name>). */
11
+ name?: string;
12
+ }
13
+ declare class McpVoice implements Voice {
14
+ private options;
15
+ name: string;
16
+ description: string;
17
+ required_permissions: Permission[];
18
+ tools: Tool[];
19
+ private client;
20
+ private transport;
21
+ private initialized;
22
+ constructor(options: McpVoiceOptions);
23
+ setup(_context: VoiceContext): Promise<void>;
24
+ teardown(): Promise<void>;
25
+ private discoverTools;
26
+ private callMcpTool;
27
+ }
28
+
29
+ export { McpVoice, type McpVoiceOptions };
package/dist/index.js ADDED
@@ -0,0 +1,117 @@
1
+ // src/index.ts
2
+ import { z } from "zod";
3
+ import { Client } from "@modelcontextprotocol/sdk/client";
4
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
+ var McpVoice = class {
6
+ constructor(options) {
7
+ this.options = options;
8
+ const lastSegment = options.server.split(/[\s/]/).filter(Boolean).at(-1) ?? "server";
9
+ this.name = options.name ?? "mcp-" + lastSegment;
10
+ }
11
+ options;
12
+ name;
13
+ description = "MCP server bridge";
14
+ required_permissions = ["network"];
15
+ tools = [];
16
+ client;
17
+ transport;
18
+ initialized = false;
19
+ async setup(_context) {
20
+ if (this.initialized) return;
21
+ const [command, ...cmdArgs] = this.options.server.split(/\s+/);
22
+ this.transport = new StdioClientTransport({
23
+ command,
24
+ args: [...cmdArgs, ...this.options.args ?? []],
25
+ env: this.options.env,
26
+ stderr: "pipe"
27
+ });
28
+ this.client = new Client(
29
+ { name: "tutti", version: "1.0.0" }
30
+ );
31
+ await this.client.connect(this.transport);
32
+ this.tools = await this.discoverTools();
33
+ this.initialized = true;
34
+ }
35
+ async teardown() {
36
+ if (this.client) {
37
+ await this.client.close();
38
+ this.client = void 0;
39
+ }
40
+ this.transport = void 0;
41
+ this.tools = [];
42
+ this.initialized = false;
43
+ }
44
+ async discoverTools() {
45
+ if (!this.client) throw new Error("MCP client not connected");
46
+ const { tools: mcpTools } = await this.client.listTools();
47
+ return mcpTools.map((mcpTool) => {
48
+ const schema = jsonSchemaToZod(mcpTool.inputSchema);
49
+ return {
50
+ name: mcpTool.name,
51
+ description: mcpTool.description ?? "",
52
+ parameters: schema,
53
+ execute: (input, _context) => {
54
+ return this.callMcpTool(mcpTool.name, input);
55
+ }
56
+ };
57
+ });
58
+ }
59
+ async callMcpTool(name, args) {
60
+ if (!this.client) {
61
+ return { content: "MCP client not connected", is_error: true };
62
+ }
63
+ const result = await this.client.callTool({ name, arguments: args });
64
+ const text = result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
65
+ return {
66
+ content: text || "(no output)",
67
+ is_error: result.isError === true
68
+ };
69
+ }
70
+ };
71
+ function jsonSchemaToZod(schema) {
72
+ const properties = schema.properties;
73
+ const required = schema.required ?? [];
74
+ if (!properties) {
75
+ return z.record(z.unknown());
76
+ }
77
+ const shape = {};
78
+ for (const [key, prop] of Object.entries(properties)) {
79
+ let field = convertType(prop);
80
+ if (prop.description) {
81
+ field = field.describe(prop.description);
82
+ }
83
+ if (!required.includes(key)) {
84
+ field = field.optional();
85
+ }
86
+ shape[key] = field;
87
+ }
88
+ return z.object(shape).passthrough();
89
+ }
90
+ function convertType(prop) {
91
+ if (prop.enum) {
92
+ const values = prop.enum;
93
+ if (values.length > 0) {
94
+ return z.enum(values);
95
+ }
96
+ }
97
+ switch (prop.type) {
98
+ case "string":
99
+ return z.string();
100
+ case "number":
101
+ return z.number();
102
+ case "integer":
103
+ return z.number().int();
104
+ case "boolean":
105
+ return z.boolean();
106
+ case "array":
107
+ return z.array(z.unknown());
108
+ case "object":
109
+ return z.record(z.unknown());
110
+ default:
111
+ return z.unknown();
112
+ }
113
+ }
114
+ export {
115
+ McpVoice
116
+ };
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { Client } from \"@modelcontextprotocol/sdk/client\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport type {\n Permission,\n Tool,\n ToolContext,\n ToolResult,\n Voice,\n VoiceContext,\n} from \"@tuttiai/types\";\n\nexport interface McpVoiceOptions {\n /** MCP server command, e.g. 'npx @playwright/mcp' */\n server: string;\n /** Additional CLI arguments appended after the server command. */\n args?: string[];\n /** Extra environment variables passed to the server process. */\n env?: Record<string, string>;\n /** Override the voice name (default: mcp-<server-name>). */\n name?: string;\n}\n\nexport class McpVoice implements Voice {\n name: string;\n description = \"MCP server bridge\";\n required_permissions: Permission[] = [\"network\"];\n tools: Tool[] = [];\n\n private client: Client | undefined;\n private transport: StdioClientTransport | undefined;\n private initialized = false;\n\n constructor(private options: McpVoiceOptions) {\n const lastSegment = options.server.split(/[\\s/]/).filter(Boolean).at(-1) ?? \"server\";\n this.name = options.name ?? \"mcp-\" + lastSegment;\n }\n\n async setup(_context: VoiceContext): Promise<void> {\n if (this.initialized) return;\n\n const [command, ...cmdArgs] = this.options.server.split(/\\s+/);\n\n this.transport = new StdioClientTransport({\n command,\n args: [...cmdArgs, ...(this.options.args ?? [])],\n env: this.options.env,\n stderr: \"pipe\",\n });\n\n this.client = new Client(\n { name: \"tutti\", version: \"1.0.0\" },\n );\n\n await this.client.connect(this.transport);\n this.tools = await this.discoverTools();\n this.initialized = true;\n }\n\n async teardown(): Promise<void> {\n if (this.client) {\n await this.client.close();\n this.client = undefined;\n }\n this.transport = undefined;\n this.tools = [];\n this.initialized = false;\n }\n\n private async discoverTools(): Promise<Tool[]> {\n if (!this.client) throw new Error(\"MCP client not connected\");\n\n const { tools: mcpTools } = await this.client.listTools();\n\n return mcpTools.map((mcpTool) => {\n const schema = jsonSchemaToZod(mcpTool.inputSchema as Record<string, unknown>);\n\n return {\n name: mcpTool.name,\n description: mcpTool.description ?? \"\",\n parameters: schema,\n execute: (input: unknown, _context: ToolContext): Promise<ToolResult> => {\n return this.callMcpTool(mcpTool.name, input as Record<string, unknown>);\n },\n };\n });\n }\n\n private async callMcpTool(\n name: string,\n args: Record<string, unknown>,\n ): Promise<ToolResult> {\n if (!this.client) {\n return { content: \"MCP client not connected\", is_error: true };\n }\n\n const result = await this.client.callTool({ name, arguments: args });\n\n // MCP result.content is an array of { type: \"text\", text: string } blocks\n const text = (result.content as { type: string; text?: string }[])\n .filter((c) => c.type === \"text\" && c.text)\n .map((c) => c.text)\n .join(\"\\n\");\n\n return {\n content: text || \"(no output)\",\n is_error: result.isError === true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON Schema → Zod converter (covers common MCP tool schemas)\n// ---------------------------------------------------------------------------\n\nfunction jsonSchemaToZod(schema: Record<string, unknown>): z.ZodType {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n const required = (schema.required as string[]) ?? [];\n\n if (!properties) {\n return z.record(z.unknown());\n }\n\n const shape: Record<string, z.ZodType> = {};\n for (const [key, prop] of Object.entries(properties)) {\n let field = convertType(prop);\n if (prop.description) {\n field = field.describe(prop.description as string);\n }\n if (!required.includes(key)) {\n field = field.optional() as unknown as z.ZodType;\n }\n shape[key] = field;\n }\n\n return z.object(shape).passthrough();\n}\n\nfunction convertType(prop: Record<string, unknown>): z.ZodType {\n if (prop.enum) {\n const values = prop.enum as string[];\n if (values.length > 0) {\n return z.enum(values as [string, ...string[]]);\n }\n }\n\n switch (prop.type) {\n case \"string\":\n return z.string();\n case \"number\":\n return z.number();\n case \"integer\":\n return z.number().int();\n case \"boolean\":\n return z.boolean();\n case \"array\":\n return z.array(z.unknown());\n case \"object\":\n return z.record(z.unknown());\n default:\n return z.unknown();\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "tuttiai-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP bridge voice for Tutti — wrap any MCP server as a Tutti voice",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/index.js",
9
+ "types": "./dist/index.d.ts"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "files": ["dist", "README.md", "LICENSE"],
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "dev": "tsup --watch",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.12.1",
24
+ "@tuttiai/types": "*",
25
+ "zod": "3.25.76"
26
+ },
27
+ "keywords": ["tutti", "ai", "agents", "orchestration", "voice", "mcp", "model-context-protocol", "bridge"],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/tuttiai/tutti",
32
+ "directory": "voices/mcp"
33
+ },
34
+ "homepage": "https://tutti-ai.com"
35
+ }