diff --git a/CMakeLists.txt b/CMakeLists.txt
index cb1e4224cf..a399e11168 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,4 +49,5 @@ add_subdirectory(assignment-client)
 add_subdirectory(domain-server)
 add_subdirectory(interface)
 add_subdirectory(tests)
+add_subdirectory(tools)
 add_subdirectory(voxel-edit)
diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake
index 1682b9cd56..6f0216de7f 100644
--- a/cmake/macros/AutoMTC.cmake
+++ b/cmake/macros/AutoMTC.cmake
@@ -9,13 +9,9 @@
 # 
 
 macro(AUTO_MTC TARGET ROOT_DIR)
-  if (NOT TARGET mtc)
-    add_subdirectory("${ROOT_DIR}/tools/mtc" "${ROOT_DIR}/tools/mtc")
-  endif ()
-  
   set(AUTOMTC_SRC ${TARGET}_automtc.cpp)
   
   file(GLOB INCLUDE_FILES src/*.h)
   
   add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND mtc -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
-endmacro()
\ No newline at end of file
+endmacro()
diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp
index 86f508ca11..0667e71fb1 100644
--- a/libraries/metavoxels/src/Bitstream.cpp
+++ b/libraries/metavoxels/src/Bitstream.cpp
@@ -33,6 +33,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(QByteArray)
 REGISTER_SIMPLE_TYPE_STREAMER(QColor)
 REGISTER_SIMPLE_TYPE_STREAMER(QScriptValue)
 REGISTER_SIMPLE_TYPE_STREAMER(QString)
+REGISTER_SIMPLE_TYPE_STREAMER(QVariant)
 REGISTER_SIMPLE_TYPE_STREAMER(QUrl)
 REGISTER_SIMPLE_TYPE_STREAMER(QVariantList)
 REGISTER_SIMPLE_TYPE_STREAMER(QVariantHash)
diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h
index 776a362717..66a42e6991 100644
--- a/libraries/metavoxels/src/Bitstream.h
+++ b/libraries/metavoxels/src/Bitstream.h
@@ -805,6 +805,8 @@ public:
     
     template<class T> JSONWriter& operator<<(const T& value) { _contents.append(getData(value)); return *this; }
     
+    void appendToContents(const QJsonValue& value) { _contents.append(value); }
+    
     void addSharedObject(const SharedObjectPointer& object);
     void addObjectStreamer(const ObjectStreamer* streamer);
     void addTypeStreamer(const TypeStreamer* streamer);
@@ -893,6 +895,8 @@ public:
     
     template<class T> JSONReader& operator>>(T& value) { putData(*_contentsIterator++, value); return *this; }
 
+    QJsonValue retrieveNextFromContents() { return *_contentsIterator++; }
+    
     TypeStreamerPointer getTypeStreamer(const QString& name) const;
     ObjectStreamerPointer getObjectStreamer(const QString& name) const { return _objectStreamers.value(name); }
     SharedObjectPointer getSharedObject(int id) const { return _sharedObjects.value(id); }
diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp
index 4a3010caf4..85be9a5c8b 100644
--- a/tests/metavoxels/src/MetavoxelTests.cpp
+++ b/tests/metavoxels/src/MetavoxelTests.cpp
@@ -255,6 +255,8 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
     jsonWriter << endRead;
     QByteArray encodedJson = jsonWriter.getDocument().toJson();
     
+    qDebug() << encodedJson;
+    
     // and read from JSON
     JSONReader jsonReader(QJsonDocument::fromJson(encodedJson), Bitstream::ALL_GENERICS);
     jsonReader >> testObjectReadA;
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000000..79db82e90f
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 2.8)
+
+# add the tool directories
+add_subdirectory(bitstream2json)
+add_subdirectory(json2bitstream)
+add_subdirectory(mtc)
diff --git a/tools/bitstream2json/CMakeLists.txt b/tools/bitstream2json/CMakeLists.txt
new file mode 100644
index 0000000000..6483c024a7
--- /dev/null
+++ b/tools/bitstream2json/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.8)
+
+if (WIN32)
+  cmake_policy (SET CMP0020 NEW)
+endif (WIN32)
+
+set(TARGET_NAME bitstream2json)
+
+set(ROOT_DIR ../..)
+set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
+
+find_package(Qt5 COMPONENTS Network Script Widgets)
+
+include(${MACRO_DIR}/SetupHifiProject.cmake)
+setup_hifi_project(${TARGET_NAME} TRUE)
+
+link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
+link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
+
+target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
diff --git a/tools/bitstream2json/src/main.cpp b/tools/bitstream2json/src/main.cpp
new file mode 100644
index 0000000000..0f299527b0
--- /dev/null
+++ b/tools/bitstream2json/src/main.cpp
@@ -0,0 +1,70 @@
+//
+//  main.cpp
+//  tools/bitstream2json/src
+//
+//  Created by Andrzej Kapolka on 6/17/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+
+#include <iostream>
+
+#include <QCoreApplication>
+#include <QDataStream>
+#include <QFile>
+
+#include <AttributeRegistry.h>
+
+using namespace std;
+
+int main (int argc, char** argv) {
+    // need the core application for the script engine
+    QCoreApplication app(argc, argv);
+    
+    if (argc < 3) {
+        cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
+        return 0;
+    }
+    QFile inputFile(argv[1]);
+    if (!inputFile.open(QIODevice::ReadOnly)) {
+        cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
+        return 1;
+    }
+    QDataStream inputData(&inputFile);
+    Bitstream input(inputData, Bitstream::FULL_METADATA, Bitstream::ALL_GENERICS);
+    
+    QFile outputFile(argv[2]);
+    if (!outputFile.open(QIODevice::WriteOnly)) {
+        cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
+        return 1;
+    }
+    JSONWriter output;
+    
+    if (argc < 4) {
+        // default type is a single QVariant
+        QVariant value;
+        input >> value;
+        output << value;
+    
+    } else {
+        for (int i = 3; i < argc; i++) {
+            int type = QMetaType::type(argv[i]);
+            if (type == QMetaType::UnknownType) {
+                cerr << "Unknown type: " << argv[i] << endl;
+                return 1;
+            }
+            const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
+            if (!streamer) {
+                cerr << "Non-streamable type: " << argv[i] << endl;
+                return 1;
+            }
+            QVariant value = streamer->read(input);
+            output.appendToContents(streamer->getJSONData(output, value));
+        }
+    }
+    
+    outputFile.write(output.getDocument().toJson());
+    
+    return 0;
+}
diff --git a/tools/json2bitstream/CMakeLists.txt b/tools/json2bitstream/CMakeLists.txt
new file mode 100644
index 0000000000..799f3bcc4c
--- /dev/null
+++ b/tools/json2bitstream/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 2.8)
+
+if (WIN32)
+  cmake_policy (SET CMP0020 NEW)
+endif (WIN32)
+
+set(TARGET_NAME json2bitstream)
+
+set(ROOT_DIR ../..)
+set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
+
+find_package(Qt5 COMPONENTS Network Script Widgets)
+
+include(${MACRO_DIR}/SetupHifiProject.cmake)
+setup_hifi_project(${TARGET_NAME} TRUE)
+
+link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
+link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
+
+target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
diff --git a/tools/json2bitstream/src/main.cpp b/tools/json2bitstream/src/main.cpp
new file mode 100644
index 0000000000..ff09bcfdc6
--- /dev/null
+++ b/tools/json2bitstream/src/main.cpp
@@ -0,0 +1,76 @@
+//
+//  main.cpp
+//  tools/json2bitstream/src
+//
+//  Created by Andrzej Kapolka on 6/17/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+
+#include <iostream>
+
+#include <QCoreApplication>
+#include <QDataStream>
+#include <QFile>
+
+#include <AttributeRegistry.h>
+
+using namespace std;
+
+int main (int argc, char** argv) {
+    // need the core application for the script engine
+    QCoreApplication app(argc, argv);
+    
+    if (argc < 3) {
+        cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
+        return 0;
+    }
+    QFile inputFile(argv[1]);
+    if (!inputFile.open(QIODevice::ReadOnly)) {
+        cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
+        return 1;
+    }
+    QJsonParseError error;
+    QJsonDocument document = QJsonDocument::fromJson(inputFile.readAll(), &error);
+    if (error.error != QJsonParseError::NoError) {
+        cerr << "Failed to read input file: " << error.errorString().toLatin1().constData() << endl;
+        return 1;
+    }
+    JSONReader input(document, Bitstream::ALL_GENERICS);
+    
+    QFile outputFile(argv[2]);
+    if (!outputFile.open(QIODevice::WriteOnly)) {
+        cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
+        return 1;
+    }
+    QDataStream outputData(&outputFile);
+    Bitstream output(outputData, Bitstream::FULL_METADATA);
+    
+    if (argc < 4) {
+        // default type is a single QVariant
+        QVariant value;
+        input >> value;
+        output << value;
+    
+    } else {
+        for (int i = 3; i < argc; i++) {
+            int type = QMetaType::type(argv[i]);
+            if (type == QMetaType::UnknownType) {
+                cerr << "Unknown type: " << argv[i] << endl;
+                return 1;
+            }
+            const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
+            if (!streamer) {
+                cerr << "Non-streamable type: " << argv[i] << endl;
+                return 1;
+            }
+            QVariant value;
+            streamer->putJSONData(input, input.retrieveNextFromContents(), value);
+            streamer->write(output, value);
+        }
+    }
+    output.flush();
+    
+    return 0;
+}